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

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

  •  
  •   xiaoming1992 · 79 天前 · 1639 次点击
    这是一个创建于 79 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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
        1
    geekdada   79 天前
    运行在 Runtime 里的还是 JS 代码,所以你在运行时拿到的类型是 string。
        2
    ochatokori   79 天前 via Android
    可能我不会 ts,用 ts 有很多这样类似的问题
    实在不行就<Vegetable>
        3
    Hypn0s   79 天前
    ```javascript
    Object.entries(list).map(([trueKey, item]) => {
    let key1 = <Vegetable>trueKey;
    let key2 = trueKey as Vegetable;
    })
    ```
    实在没办法
        4
    azh7138m   79 天前   ♥ 1
    entries 定义是
    entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
    entries(o: {}): [string, any][];
    你得自己提供一个 entries 来 hack 这个 key 的类型
        5
    azh7138m   79 天前
    目前在 ts 里面构造出一个数组长度是一个对象 key 的数量,这种操作很难做通用实现
    value 类型固定的,给个 xjb 的做法的话
    在代码前面写一个
    interface ObjectConstructor {
    entries<O>(o: O): [keyof O, O[keyof O]][];
    }
    后面的 entries 就能拿到类型了
    但是我不推荐这样子写,因为不通用
    如果场景足够单一,可以补充一个 entries 的定义,如果还有别的场景,建议手动指定 key 的类型
        6
    wawaforya   79 天前
    ```
    Object.entries(list).map(([key, item]: [Vegetable, { price: number; num: number }]) => {
    // 可以给参数加个类型定义
    });
    ```
        7
    xiaoming1992   79 天前
    @wawaforya 没用,提示不能将 string 赋给 Vegetable
        8
    jatai   79 天前 via Android
    什么? ts 也设置类型? 前端高手不都是用 any 吗
        9
    xiaoming1992   79 天前
    @wawaforya 没用,提示不能将 string 分配给 Vegetable 类型;

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

    @azh7138m 写这样的 interface 给我的感觉好像是 js 里面重写原型方法一样,不太喜欢这样写,虽然这样确实解决问题;
        10
    xiaoming1992   79 天前
    可能因为我菜吧 /(ㄒoㄒ)/~~
        11
    azh7138m   79 天前
    @xiaoming1992 并不,他们不是一个概念,补充一个新的签名是重载,js 里面没有这个概念
        12
    xiaoming1992   79 天前
    @azh7138m 不是说`重载`必须要将`函数定义`紧挨着`interface`写吗?还可以半中间插进来?王自如觉得很 awesome👍
        14
    optional   79 天前
    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;
    };
    }
    但是这又是不合法的。
        15
    wawaforya   79 天前   ♥ 2
    @xiaoming1992 , 我这边可以的啊,难道是版本问题?我 typescript 的版本是 3.5.3

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


    @azh7138m 感谢,等我吃完饭回去看看。
        17
    optional   79 天前
    @xiaoming1992 不会报错
        18
    zbinlin   79 天前
    entries 的签名写死了 string,你试试加上这个自动推导的签名:

    declare global {
    interface ObjectConstructor {
    entries<S extends string, T>(list: { [key in S]: T }): [S, T][];
    }
    }
        19
    xiaoming1992   79 天前 via Android
    @zbinlin .如果这么搞的话,那什么 map foreach 什么的都要声明。。。
        20
    zbinlin   79 天前
    @xiaoming1992 不需要呀,为什么需要,map, foreach 这些已经能正确地推导出数组里的类型的。
        21
    xiaoming1992   78 天前 via Android
    @zbinlin 对,被搞懵了
        22
    Oucreate   78 天前
    @azh7138m #4
    > entries 定义是
    > entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][]

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


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

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

    或许 @optional 是对的,我不应该默认生成的 key 是 Vegetable,应该再次校验一下。
        25
    Oucreate   78 天前
    @azh7138m 呃,我还是不太明白……

    为什么不都是第二种定义的 `any`,毕竟值不都是任意类型的么,所以会有什么特殊的情形需要 `<T>`?
        26
    Oucreate   78 天前
    修正:
    “毕竟值不都是任意类型的么”

    “毕竟值不都 *可以* 是任意类型的么”
        27
    azh7138m   78 天前
    @xiaoming1992
    - 我没有写 extends


    @Oucreate
    特殊的情形 -> 把对象当 Map<string, T>用的时候
        28
    lhc70000   78 天前
    ```
    ```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 的类型。
        29
    Oucreate   78 天前
    @azh7138m 这种情形下指定为 `<T>` 是为了统一和方便吗?
        30
    azh7138m   78 天前   ♥ 1
    @Oucreate 一般是为了更好 /多的类型检查
        31
    xiaoming1992   78 天前 via Android   ♥ 1
    @lhc70000 对,我后来就这么写的,省得到里面一个一个的给 key 加 as。

    @Oucreate 对于复杂一些的对象,有类型检查和提示更方便啊,我现在恨不得所有变量都尽可能精确地描述它的类型。正常情况下 key 确实是 string,但是当我的 key 受到业余限定,只有这么确定的几种的话,用 a | b | c 来限定它们的类型会更方便。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1012 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 25ms · UTC 22:38 · PVG 06:38 · LAX 14:38 · JFK 17:38
    ♥ Do have faith in what you're doing.