在尝试用 TypeScript 写 node 服务,有一段插入数据库的代码如下:

interface DBRecord {
  id: number;
  title: string;
  url: string;
  guid: string;
};

/**
 * 往 DB 中插入新条目,返回新增行数
 * @param itemsList 需插入的条目
 */
export async function insertItems(itemsList: DBRecord[]) {
  const result = await Promise.all(
    itemsList.map((item) => {
      const sql = `INSERT INTO "records" ("id", "title", "url", "guid") VALUES (?, ?, ?, ?);`;
      return DB.run(
        sql,
        ["id", "title", "url", "guid"].map((key) => item[key])
      );
    })
  );
  return result.reduce((accu, curr) => accu + curr.changes, 0);
}

编译时出现了以下的报错:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'DBRecord'.
No index signature with a parameter of type 'string' was found on type 'DBRecord'.ts(7053)

截图:

在 reddit 找到一个类似的问题:
https://www.reddit.com/r/typescript/comments/edzgtw/help_no_index_signature_with_a_parameter_of_type/

It's because key has type string, but the single object in temp only accepts values of type "object" | "object2" as keys.

对应到我这里的上下文,大概意思是:key 是 string 的类型,但我的 item 访问属性时候只接受 "id" | "title" | "url" | "guid",然后类型不对应。

该如何解决这个报错呢?

    截图第45行改:

    (["id","title","url","guid"] as DBRecord[])

    然后引入一下 DBRecord 的定义,typescript 默认把数组类型推断为字符串数组类,这里需要“收窄”它的类型定义

      贴一个最小可复现的代码,减少干扰 @LittleboyHarry

      interface DBRecord {
        id: number;
        title: string;
        url: string;
        guid: string;
      };
      
      const item: DBRecord = {
        id: 1,
        title: 'tt',
        url: 'http://baidu.com',
        guid: 'http://baidu.com',
      };
      
      ["id", "title", "url", "guid"].map((key) => item[key])

      然后在 item[key] 这里会出现一样的报错:

        {id, title, url, guid} = item;
        const parms = [id, title, url, guid];

        string indexed的要这样。

        interface DBRecord {
          [key: string]: number|string;
        };

        但是这样就没有了typescript的意义了。

          LittleboyHarry 我想是不是因为 DB.run 第二个参数的类型只接受 string[] 呢?

          咦,没错,的确是只接受 string[]

            0x709394 0x0001 的确是这里的锅,类型没有匹配

            试了下,把 DB.run 定义的第二个参数改成 any 之后,map 的操作改成:

            (["id", "title", "url", "guid"] as const).map((key) => item[key])

            也可以避免报错。

              0x0001 最终这么解决了,解构 + number -> string:

              /**
               * 往 DB 中插入新条目,返回新增行数
               * @param itemsList 需插入的条目
               */
              export async function insertItems(itemsList: DBRecord[]) {
                const result = await Promise.all(
                  itemsList.map((item) => {
                    const sql = `INSERT INTO "records" ("id", "title", "url", "guid") VALUES (?, ?, ?, ?);`;
                    const { id, title, url, guid } = item;
                    return DB.run(
                      sql,
                      [id + '', title, url, guid],
                    );
                  })
                );
                return result.reduce((accu, curr) => accu + curr.changes, 0);
              }

              用解构其实还更加优雅一些,脑子没转过来哈哈!

              感谢各位 😇

              0x0001 TypeScript 的奇妙用法还真多,这个 as const 的用法我之前没见过... 搜了一下是 TypeScript 3.4 新增的特性 const assertions ,可以收窄字面量类型,感觉非常好用

                LittleboyHarry 我还真没在 hooks 项目中用过... 对于数组和对象如果想要 readonly 我会套自己定义的类型(逃
                有没有开源项目中用到的例子可以参考下?

                  LittleboyHarry 感谢!我昨晚在 github 里搜了下代码也基本是用在类似 redux 状态、常属性对象等。这个用在 hooks 里收窄类型确实非常方便了,而我比较头铁,所有类型都是自己定义(就像 @umijs/hooks 里的 useDrop 一样)(逃

                  看来以后可以用 const assertions 改善生产力(

                  昨晚我还发掘了一个用法,当成枚举用(然而类型可读性还是屎):

                  const XhrStates = {
                      UNSENT: 0,
                      OPENED: 1,
                      HEADERS_RECEIVED: 2,
                      LOADING: 3,
                      DONE: 4,
                  } as const;
                  
                  type ExtractValue<T> = T[keyof T];
                  type XhrReadyState = ExtractValue<typeof XhrStates>;
                  
                  var xhr = new XMLHttpRequest();
                  xhr.readyState as XhrReadyState === XhrStates.DONE

                    hsxfjames 搞复杂了:

                    enum XhrStates {
                      UNSENT = 0,
                      OPENED = 1,
                      HEADERS_RECEIVED = 2,
                      LOADING = 3,
                      DONE = 4,
                    }
                    
                    let xhr = new XMLHttpRequest();
                    xhr.readyState === XhrStates.DONE;
                    
                    let value: XhrStates = 1; // 整数可以直接变成枚举

                      LittleboyHarry 有区别的,两者的类型提示信息不同

                      虽然我觉得这两种的可读性都是狗屎(逃

                      0x0001 栗子栗子,我自己写的又不能公开(逃
                      主要是考虑传参时的类型,希望别人可以直接在参数上看到有哪些枚举可以用,最好还能带上 JS Doc ,但是 enum 不支持
                      (好像已经是另一个话题了,等有空去 github 提个 issue ,这个 enum 真是太难用了)

                      13 天 后
                      3 年 后

                      © 2018-2025 0xFFFF