TypeScript 的索引定义和原有字段类型冲突有什么技巧? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
makelove
V2EX    TypeScript

TypeScript 的索引定义和原有字段类型冲突有什么技巧?

  •  
  •   makelove 2023-07-07 08:13:41 +08:00 2058 次点击
    这是一个创建于 895 天前的主题,其中的信息可能已经有所发展或是发生改变。

    PlayGround

    type Bar = { name: string } & { // A: 这行为什么必需呢?官方文档里也没写清楚 [key: string]: Date } declare const bar: Bar let a = bar.name // 正确的得到 string let b = bar['x'] // 正确的得到 Date const bar2: Bar = { name: 'xx' } // B: 这里为什么报错? const bar3 = { name: 'xx' } as Bar // C: 难道所有地方都要强制转换? let b2 = bar2['x'] 
    12 条回复    2023-07-31 17:08:09 +08:00
    Iefty
        1
    Iefty  
       2023-07-07 08:52:23 +08:00
    [key: string]: string | Date
    enpitsulin
        2
    enpitsulin  
       2023-07-07 09:25:19 +08:00   1
    因为索引类型定义就不应该和其他属性定义冲突啊,索引类型其他属性类型

    ```typescript
    interface Bar {
    name: string
    [key: string]: Date | string
    }
    ```
    seashell2000
        3
    seashell2000  
       2023-07-07 09:33:40 +08:00
    use "Symbol" for name
    makelove
        4
    makelove  
    OP
       2023-07-07 12:49:51 +08:00
    @Iefty 这样的话还不如强制转换呢,起换读属性的时候类型正确的
    makelove
        5
    makelove  
    OP
       2023-07-07 12:57:24 +08:00
    @enpitsulin 这不是应不应该的问题,是现实中需要怎么办?
    我要为一个第三方库写个类型定义,这个库有个类型就是有一些预定义的属性,还有其它动态扩展属性,这些个动态扩展属性就放在这个类型对象上
    enpitsulin
        6
    enpitsulin  
       2023-07-07 13:51:38 +08:00
    @makelove #5 用了索引就应该把类型定义和别的属性不冲突啊?实际问题的话,你要是有什么 X 就别问 Y
    Belmode
        7
    Belmode  
       2023-07-12 18:15:43 +08:00
    这段代码在 TypeScript 中并没有明显的语法错误,但它存在潜在的问题。

    问题在于类型 Bar 定义了一个属性 name 的固定类型为 string ,以及一个索引签名 [key: string]: Date ,允许任意字符串键名对应的值为 Date 类型。

    这种定义可能会导致类型不一致或产生意外行为。因为在 Bar 类型中,name 属性被指定为 string 类型,而索引签名允许任意字符串键名对应的值为 Date 类型。这样就引入了潜在的类型冲突。

    举个例子:

    typescript
    const bar: Bar = {
    name: 'John',
    age: new Date() // 错误,age 不是 Date 类型
    };
    在上述示例中,我们试图将一个具有 'name' 和 'age' 属性的对象赋值给 Bar 类型的变量 bar ,但是在 Bar 类型中并没有定义 age 属性,并且索引签名的值类型是 Date 。因此,这样的赋值将会导致类型错误。

    为了解决这个问题,你可以考虑`重新设计类型定义,确保属性和索引签名的类型一致`,或者`根据实际需求修改类型定义`。具体如何修改取决于你的使用场景和预期行为。

    GPT 说的很明确,你用法不对。
    chnwillliu
        8
    chnwillliu  
       2023-07-31 06:54:11 +08:00 via Android
    因为对象的 key 可能来自运行时,ts 无法推断。试想如果取 obj[key] 那 ts 应该推断其类型为 Date 还是 string ? 如果算做 Date 那万一 key 的值在运行时其实是 'name' 呢?
    makelove
        9
    makelove  
    OP
       2023-07-31 08:06:17 +08:00
    @chnwillliu ts 访问的时候可以区分 .xxx 访问和 [xxx] 索引访问,照你这么说的话为什么现在 bar.name 和 bar['xxx' as string] 可以得到正确的类型?
    chnwillliu
        10
    chnwillliu  
       2023-07-31 15:29:33 +08:00
    @makelove 用 intersection type 虽然规避了同一个 interface 内声明的字段类型必须兼容 index signature 的 TS 报错,但其实这是一种错误用法,不报错可能就像你说的,.xxx 和 索引访问在进行类型交叉的时候互不干扰。

    这个所谓的 index signature 必须兼容所有字段的设定,是为了规避在 bar['na'+'me'].getMonth() 这种场景下产生错误推断。

    这两种类型交叉后,更准确地,我觉得 bar.name 应该推断为 string & Date ,但 JS 中怎么构造一个既是 string 又是 Date 的变量呢。

    const bar2: Bar = { name: 'xx' as string & Date }

    你看这样就不报错了,说明右边的 literal object 必须同时满足 Bar 的两个类型才能赋值。但是因为强行 as ,运行时 bar['na'+'me'].getMonth() 仍然要报错。

    类型要安全那就只能加一层,把 name 摘出来:

    interface Bar {
    name: string;
    dates: {[key: string]: Date}
    }

    或者像 #3 说的,name 用 symbol 。

    module A {
    const name = Symbol('name')

    interface Bar {
    [name]: string;
    [key: string]: Date;
    }

    export const bar: Bar = {
    [name]: 'test',
    a: new Date,
    bar: new Date
    };
    }
    chnwillliu
        11
    chnwillliu  
       2023-07-31 15:43:03 +08:00   1
    看到 OP 说是给第三方 JS 添加类型,那说明它在运行时是有问题的

    declare const bar: Bar
    let key = 'name';
    bar[key] = new Date();
    typeof bar.name // ??

    key 如果是任意字符串,那它就有可能是 ‘name’。如果说运行时保证了 key 一定不是 name ,但 TS 不知道,你只能

    interface Bar {
    name: string;
    [key: string]: string | Date;
    }

    然后在每次 bar[key] 的时候告诉 TS 这里 key 不会是 name 所以类型一定是 Date 。
    makelove
        12
    makelove  
    OP
       2023-07-31 17:08:09 +08:0
    @chnwillliu 就是一个把 xml 转 js 的第三方。它的设定是把 xml 节点转成 js 对象,属性放在 $,文本放在 "_",子节点名直接作为属性。
    所以呢,最终这个对象是
    { $: { ... }, _: 'node text', xxx: { ... }, yyy: { ... } }, 这些 xxx,yyy 都是动态的,只有 $ 和 _ 这二个属性可以保证是预定的类型。
    用的时候 bar.$ 可以确定是个属性对象类型,不可能是别的。

    所以,我需要的是用 $ 或 _ 这二个属性的时候是预设的类型,目前 ts 是可以做到的,因为它区分了用 .xxx 字面直接访问和 [xxx] 动态访问。当然了你说是误用也是有可能的,我不知道 ts 设计区分这二者是什么目的。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5454 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 45ms UTC 03:33 PVG 11:33 LAX 19:33 JFK 22:33
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86