V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
xiaoming1992
V2EX  ›  JavaScript

typescript 在 Object.entries 的回调中丢失了类型怎么办?

  •  
  •   xiaoming1992 · 2019-08-27 16:33:26 +08:00 · 6201 次点击
    这是一个创建于 1917 天前的主题,其中的信息可能已经有所发展或是发生改变。
    type Vegetable = "茄子" | "黄瓜" | "西红柿"
    
    type List = {
      [key in Vegetable]: {
        price: number;
        num: number;
      };
    }
    
    const list: List = {
      "茄子": {
        price: 5,
        num: 2
      },
      "西红柿": {
        price: 5,
        num: 2
      },
      "黄瓜": {
        price: 5,
        num: 2
      },
    }
    
    Object.entries(list).map(([key, item]) => {
      // 这儿的 key 不再是 Vegetable, 而是 string, 怎么能维持它的 Vegetable 类型呢?
    })
    
    31 条回复    2019-08-29 00:56:41 +08:00
    geekdada
        1
    geekdada  
       2019-08-27 16:46:51 +08:00
    运行在 Runtime 里的还是 JS 代码,所以你在运行时拿到的类型是 string。
    ochatokori
        2
    ochatokori  
       2019-08-27 16:49:26 +08:00 via Android
    可能我不会 ts,用 ts 有很多这样类似的问题
    实在不行就<Vegetable>
    Hypn0s
        3
    Hypn0s  
       2019-08-27 16:51:53 +08:00
    ```javascript
    Object.entries(list).map(([trueKey, item]) => {
    let key1 = <Vegetable>trueKey;
    let key2 = trueKey as Vegetable;
    })
    ```
    实在没办法
    azh7138m
        4
    azh7138m  
       2019-08-27 16:52:16 +08:00   ❤️ 1
    entries 定义是
    entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
    entries(o: {}): [string, any][];
    你得自己提供一个 entries 来 hack 这个 key 的类型
    azh7138m
        5
    azh7138m  
       2019-08-27 17:04:45 +08:00
    目前在 ts 里面构造出一个数组长度是一个对象 key 的数量,这种操作很难做通用实现
    value 类型固定的,给个 xjb 的做法的话
    在代码前面写一个
    interface ObjectConstructor {
    entries<O>(o: O): [keyof O, O[keyof O]][];
    }
    后面的 entries 就能拿到类型了
    但是我不推荐这样子写,因为不通用
    如果场景足够单一,可以补充一个 entries 的定义,如果还有别的场景,建议手动指定 key 的类型
    wawaforya
        6
    wawaforya  
       2019-08-27 17:05:29 +08:00
    ```
    Object.entries(list).map(([key, item]: [Vegetable, { price: number; num: number }]) => {
    // 可以给参数加个类型定义
    });
    ```
    xiaoming1992
        7
    xiaoming1992  
    OP
       2019-08-27 17:29:35 +08:00
    @wawaforya 没用,提示不能将 string 赋给 Vegetable
    jatai
        8
    jatai  
       2019-08-27 17:33:05 +08:00 via Android
    什么? ts 也设置类型? 前端高手不都是用 any 吗
    xiaoming1992
        9
    xiaoming1992  
    OP
       2019-08-27 17:33:45 +08:00
    @wawaforya 没用,提示不能将 string 分配给 Vegetable 类型;

    @Hypn0s 我确实是这样做的,但是如果很多地方要用到这个东西,每一处都要写很烦;

    @azh7138m 写这样的 interface 给我的感觉好像是 js 里面重写原型方法一样,不太喜欢这样写,虽然这样确实解决问题;
    xiaoming1992
        10
    xiaoming1992  
    OP
       2019-08-27 17:34:19 +08:00
    可能因为我菜吧 /(ㄒoㄒ)/~~
    azh7138m
        11
    azh7138m  
       2019-08-27 17:43:14 +08:00
    @xiaoming1992 并不,他们不是一个概念,补充一个新的签名是重载,js 里面没有这个概念
    xiaoming1992
        12
    xiaoming1992  
    OP
       2019-08-27 17:56:22 +08:00
    @azh7138m 不是说`重载`必须要将`函数定义`紧挨着`interface`写吗?还可以半中间插进来?王自如觉得很 awesome👍
    optional
        14
    optional  
       2019-08-27 18:19:26 +08:00
    type List = {
    [key in Vegetable]: {
    price: number;
    num: number;
    };
    }
    这只能保证 list[Vegetable] 的 type 是 {price,num},并不能保证所有 key 都是{price,num}
    不信你试试
    const list = {} as List
    const o = list['any']
    list['xx'] = 1;
    是合法的,list['any'] 的类型是 any

    能保证的是
    type List = {
    [key : Vegetable]: {
    price: number;
    num: number;
    };
    }
    但是这又是不合法的。
    wawaforya
        15
    wawaforya  
       2019-08-27 18:27:08 +08:00   ❤️ 2
    @xiaoming1992 , 我这边可以的啊,难道是版本问题?我 typescript 的版本是 3.5.3

    PS: List 其实可以用 Record 来定义,Record<Vegetable, { price: number; num: number }>
    xiaoming1992
        16
    xiaoming1992  
    OP
       2019-08-27 18:41:28 +08:00 via Android
    @optional 声明里面没有"xx"这个 key,你添加`list['xx'] = 1;`不会报错吗?难道是因为我开了严格模式的缘故?等我回去看看。


    @azh7138m 感谢,等我吃完饭回去看看。
    optional
        17
    optional  
       2019-08-27 18:45:41 +08:00
    @xiaoming1992 不会报错
    zbinlin
        18
    zbinlin  
       2019-08-27 22:00:17 +08:00
    entries 的签名写死了 string,你试试加上这个自动推导的签名:

    declare global {
    interface ObjectConstructor {
    entries<S extends string, T>(list: { [key in S]: T }): [S, T][];
    }
    }
    xiaoming1992
        19
    xiaoming1992  
    OP
       2019-08-27 23:08:09 +08:00 via Android
    @zbinlin .如果这么搞的话,那什么 map foreach 什么的都要声明。。。
    zbinlin
        20
    zbinlin  
       2019-08-28 00:12:07 +08:00
    @xiaoming1992 不需要呀,为什么需要,map, foreach 这些已经能正确地推导出数组里的类型的。
    xiaoming1992
        21
    xiaoming1992  
    OP
       2019-08-28 06:57:47 +08:00 via Android
    @zbinlin 对,被搞懵了
    Oucreate
        22
    Oucreate  
       2019-08-28 13:47:12 +08:00   ❤️ 1
    @azh7138m #4
    > entries 定义是
    > entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][]

    虽然的确如此(可见于文件`lib.es2017.object.d.ts`),但我想不出需要 `<T>` 的情形,你能不能举个例子?谢谢啦!
    azh7138m
        23
    azh7138m  
       2019-08-28 13:57:39 +08:00
    @Oucreate 你就当它是 Map<string, T>吧
    xiaoming1992
        24
    xiaoming1992  
    OP
       2019-08-28 15:03:08 +08:00
    @wawaforya 才知道 Record 这么个东西,对我的类型定义是个很好的补充,感谢。虽然还是没什么卵用。。。


    @zbinlin 写到 global 上不知道会不会对编译产生什么负担?

    不知道怎么,按照 @azh7138m 写的,添加一个定义
    ``` typescript
    interface ObjectConstructor {
    entries<S extends string, T>(o: Record<S, T>): [S, T][];
    }
    ```
    没起作用。

    或许 @optional 是对的,我不应该默认生成的 key 是 Vegetable,应该再次校验一下。
    Oucreate
        25
    Oucreate  
       2019-08-28 15:08:52 +08:00   ❤️ 1
    @azh7138m 呃,我还是不太明白……

    为什么不都是第二种定义的 `any`,毕竟值不都是任意类型的么,所以会有什么特殊的情形需要 `<T>`?
    Oucreate
        26
    Oucreate  
       2019-08-28 15:12:15 +08:00
    修正:
    “毕竟值不都是任意类型的么”

    “毕竟值不都 *可以* 是任意类型的么”
    azh7138m
        27
    azh7138m  
       2019-08-28 15:45:31 +08:00
    @xiaoming1992
    - 我没有写 extends


    @Oucreate
    特殊的情形 -> 把对象当 Map<string, T>用的时候
    lhc70000
        28
    lhc70000  
       2019-08-28 16:17:15 +08:00
    ```
    ```ts
    type Vegetable = "茄子" | "黄瓜" | "西红柿";
    type Value = { price: number, num: number };

    const list: Record<Vegetable, Value> = {
    "茄子": {
    price: 5,
    num: 2
    },
    "西红柿": {
    price: 5,
    num: 2
    },
    "黄瓜": {
    price: 5,
    num: 2
    },
    };

    (Object.entries(list) as [Vegetable, Value][]).map(([key, item]) => {
    // ...
    });
    ```

    要是我的话就这么写。

    或者就直接使用 Map,这样 list.entries() 能保持 key 的类型。
    Oucreate
        29
    Oucreate  
       2019-08-28 18:04:23 +08:00
    @azh7138m 这种情形下指定为 `<T>` 是为了统一和方便吗?
    azh7138m
        30
    azh7138m  
       2019-08-28 18:55:55 +08:00   ❤️ 1
    @Oucreate 一般是为了更好 /多的类型检查
    xiaoming1992
        31
    xiaoming1992  
    OP
       2019-08-29 00:56:41 +08:00 via Android   ❤️ 1
    @lhc70000 对,我后来就这么写的,省得到里面一个一个的给 key 加 as。

    @Oucreate 对于复杂一些的对象,有类型检查和提示更方便啊,我现在恨不得所有变量都尽可能精确地描述它的类型。正常情况下 key 确实是 string,但是当我的 key 受到业余限定,只有这么确定的几种的话,用 a | b | c 来限定它们的类型会更方便。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3239 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 12:58 · PVG 20:58 · LAX 04:58 · JFK 07:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.