【问题标题】:Using TypeScript, how do I strongly type mysql query results使用TypeScript,我如何强类型mysql查询结果
【发布时间】:2019-02-07 23:34:37
【问题描述】:

我是 Typescript 的新手,不知道我是否正确输入了查询结果。这是我的代码的精髓...

import mysql2, {Pool} from "mysql2";
const pool: Pool = mysql2.createPool({...}).promise();

interface IUser {
    uid   : number;
    uname : string;
}

class UserController {

    public async getUser(id: number): Promise<IUser> {
        const [rows]: Array<{rows: IUser}> = await pool.query(
            "SELECT * FROM `users` WHERE `email` = ?",["me@me.org"]);        
        return rows;
    }
}

TypeScript 编译器 (3.3.1) 抱怨我的 return rows 语句。

TS2740:类型“{rows: IUser;}”缺少以下属性 输入“IUser”:uid 和 uname。

如果我忽略// @ts-ignore 的返回,一切都会很好。我可以很好地找回我的对象​​,没有任何错误。

我做错了吗?


我做了一些更改,但我真的很困惑为什么 TypeScript 不抱怨。这似乎一点也不对。

    class UserController {

        public async getUser(id: number): Promise<{rows: IUser}> {
            const [rows]: Array<{rows: IUser}> = await pool.query(
                "SELECT * FROM `users` LIMIT 3");        
            return rows;
        }
    }

这样好吗???


所以,不,那都是错的。

当我想到 query 返回 [rows, fields] 时,它开始变得更有意义了。我认为 @joesph-climber 在语法上做了一些调整是正确的。

这行得通,对我来说很有意义......

    class UserController {

        public async getUser(id: number): Promise<Array<{rows: IUser}>> {
            const [rows]: [Array<{rows: IUser}>] = await pool.query(
                "SELECT * FROM `users` LIMIT 3");        
            return rows;
        }
    }

这也有效,而且可能更容易理解。

    class UserController {

        public async getUser(id: number): Promise<IUser[]> {
            const [rows]: [IUser[]] = await pool.query(
                "SELECT * FROM `users` LIMIT 3");        
            return rows;
        }
    }

【问题讨论】:

  • {rows: IUser}IUser 不兼容。

标签: typescript


【解决方案1】:

使用 mysql2@1.7.0、typescript@3.6.3、@types/mysql2

接口必须扩展RowDataPacket:

interface IUser extends RowDataPacket {
  ssid: string
}

从那里你可以传入类型:

const [rows]: [IUser[], FieldPacket[]] = await connection.query<IUser[]>("SELECT ssid FROM user", [])

或者干脆

const [rows] = await connection.query<IUser[]>("SELECT ssid FROM user", [])

【讨论】:

  • RowDataPacket 的问题是它太松了。看它的 d.t.s.问题:一旦你扩展了行接口 - i。 e. IUser 在您的示例中 - 任何严格的类型都消失了,因此您可以对返回的行对象执行任何操作...row[0].whateverProperty 很好,TS 不会抱怨。
  • @dmudro 但实际上我认为没有更好的方法:/
【解决方案2】:

在尝试设置稍后将要使用的变量时,使用 pool.query&lt;type&gt; 对我来说还不够,因为数据类型 RowDataPacket 仍然侵犯了我的类型...

我最终为poolquery 函数创建了一个包装器。看起来像这样:

type Row = import("mysql2").RowDataPacket
type Ok = import("mysql2").OkPacket
type dbDefaults = Row[] | Row[][] | Ok[] | Ok
type dbQuery<T> = T & dbDefaults

export const db = {
  query: async <T>(query: string, params?: Array<any>): Promise<[T, any]> => {
    return pool.promise().query<dbQuery<T>>(query, params)
  },
}

这允许我在项目的其他部分使用query 函数,类似于其他答案

let result = await db.query<desiredType>("SELECT", [optionalArgs])

但它会剥离 RowDataPacket 等的类型信息...

【讨论】:

    【解决方案3】:

    我遇到了同样的错误,所以我在 google 中搜索并创建了这个示例:https://github.com/types/mysql2/blob/master/test/promise.ts

    所以我的代码是这样的

        const [user] = await pool.query<RowDataPacket[]>("SELECT * FROM Users WHERE email = ?", [email]);
            if(!user.length) {
                throw {
                    error: true,
                    message: 'no user founded'
                } 
            }
    

    有了这个,我可以使用vs代码的自动补全并且没有错误

    【讨论】:

    • 我收到一条错误消息 Type 'Query' must have a '[Symbol.iterator]()' method that returns an iterator. [user] 下方有红色波浪线 ... :(
    【解决方案4】:

    已接受答案中的方法对我不起作用(typescript@3.5.2mysql2@1.6.5)。这段代码

    interface IUserRow {
      ssid: string
    }
    
    const [rows]: [IUserRow[]] = await pool.query(
      'select ssid from user where id = ?',
      [session.userId],
    )
    

    给我

    error TS2322: Type '[RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[], FieldPacket[]]' is not assignable to type '[IUserRow[]]'.
      Types of property '0' are incompatible.
        Type 'RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[]' is not assignable to type 'IUserRow[]'.
          Type 'RowDataPacket[]' is not assignable to type 'IUserRow[]'.
            Property 'ssid' is missing in type 'RowDataPacket' but required in type 'IUserRow'.
    

    [rows] 突出显示。

    我能够让一切正常工作:

    interface IUserRow {
      ssid: string
    }
    
    const [rows] = await pool.query(
      'select ssid from user where id = ?',
      [session.userId],
    )
    
    // This first if statement is a type guard against rows possibly being OkPacket
    if (Array.isArray(rows) && rows.length > 0) {
      // The casting works fine here
      const userRow = rows[0] as IUserRow
      if (userRow.ssid !== session.ssid) {
        ...
      }
    }
    

    我不是打字稿专家,所以可能有更好的方法来做到这一点!不过,如果他们对接受的答案也有问题,我希望我可以潜在地挽救他们的挫败感。

    【讨论】:

    • 试试const [rows] = await pool.query&lt;IUserRow[]&gt;(
    【解决方案5】:
    // the function should return a Promise that resolves to 
    { uid: number, uname: string }
    
    // but typeof rows is 
    { rows: { uid: number, uname: string } }
    

    执行示例,我得到的结果是这样的:

    [ TextRow { uid: 1, uname: 'foo', email: 'foo@mail.com' } ]
    

    所以 pool.query 返回一个以 IUser 数组作为第一个元素的数组。

    返回多个用户:

    class UserController {
        // returns all users
        public async getUsers(): Promise<Array<IUser>> {
            const [rows]: [Array<IUser>] = await pool.query(
                    "SELECT * FROM `user`", []);
            return rows; // rows is Array<IUser> so it matches the promise type
        }
    }
    

    返回特定用户:

    class UserController {
        public async getUser(id: number): Promise<IUser> { // Note the 
            const [rows]: [Array<IUser>] = 
                await pool.query("SELECT * FROM `user` WHERE `email` = ?",
                                ["foo@mail.com"]);
            // If email is unique as it would be expected it will 
            // return a single value inside an array
            // rows is still Array<IUser>
            return rows[0]; // rows[0] is an IUser 
        }
    }
    

    【讨论】:

    • 我将查询更改为返回多行,并且可以看到它正在返回....[TextRow{},TextRow{},TextRow{}],这似乎是一个 TextRow 对象数组,所以也许我不明白 [Array] 表示;当我看到它时,我认为一个具有单个元素的数组是一个 IUser 对象数组。当我尝试您的建议时,TypeScript 抱怨函数返回类型,Promise&lt;Array&lt;ICwolDbUser&gt;&gt; 说“Array 禁止用于简单类型。请改用 T[]”。它抱怨return rows“类型 {rows: IUser}[] 不可分配给类型 IUser[]
    • 如果您想将答案从Array&lt;IUser&gt; 调整为IUser[],我会将您的答案标记为正确。感谢您的帮助。
    • 两者含义相同。但我同意不使用相同的术语可能会有点混乱。 :-)
    • 这对我不起作用:error TS2322: Type '[RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader, FieldPacket[]]' is not assignable to type '[IUser[]]'. Source has 2 element(s) but target allows only 1.
    【解决方案6】:

    觉得这个效果不错

    import {RowDataPacket} from 'mysql2'
    
          const [rows]= await pool.query<IUser[] & RowDataPacket[][]>(
              "SELECT * FROM `users` LIMIT 3");        
    

    【讨论】:

    • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
    猜你喜欢
    • 2021-12-07
    • 2015-10-24
    • 2019-12-17
    • 2020-12-27
    • 2019-10-09
    • 1970-01-01
    • 2023-02-10
    • 2020-11-19
    • 1970-01-01
    相关资源
    最近更新 更多