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']
1
Iefty 2023-07-07 08:52:23 +08:00
[key: string]: string | Date
|
2
enpitsulin 2023-07-07 09:25:19 +08:00 1
因为索引类型定义就不应该和其他属性定义冲突啊,索引类型≼其他属性类型
```typescript interface Bar { name: string [key: string]: Date | string } ``` |
3
seashell2000 2023-07-07 09:33:40 +08:00
use "Symbol" for name
|
5
makelove OP @enpitsulin 这不是应不应该的问题,是现实中需要怎么办?
我要为一个第三方库写个类型定义,这个库有个类型就是有一些预定义的属性,还有其它动态扩展属性,这些个动态扩展属性就放在这个类型对象上 |
6
enpitsulin 2023-07-07 13:51:38 +08:00
@makelove #5 用了索引就应该把类型定义和别的属性不冲突啊?实际问题的话,你要是有什么 X 就别问 Y
|
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 说的很明确,你用法不对。 |
8
chnwillliu 2023-07-31 06:54:11 +08:00 via Android
因为对象的 key 可能来自运行时,ts 无法推断。试想如果取 obj[key] 那 ts 应该推断其类型为 Date 还是 string ? 如果算做 Date 那万一 key 的值在运行时其实是 'name' 呢?
|
9
makelove OP @chnwillliu ts 访问的时候可以区分 .xxx 访问和 [xxx] 索引访问,照你这么说的话为什么现在 bar.name 和 bar['xxx' as string] 可以得到正确的类型?
|
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 }; } |
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 。 |
12
makelove OP @chnwillliu 就是一个把 xml 转 js 的第三方。它的设定是把 xml 节点转成 js 对象,属性放在 $,文本放在 "_",子节点名直接作为属性。
所以呢,最终这个对象是 { $: { ... }, _: 'node text', xxx: { ... }, yyy: { ... } }, 这些 xxx,yyy 都是动态的,只有 $ 和 _ 这二个属性可以保证是预定的类型。 用的时候 bar.$ 可以确定是个属性对象类型,不可能是别的。 所以,我需要的是用 $ 或 _ 这二个属性的时候是预设的类型,目前 ts 是可以做到的,因为它区分了用 .xxx 字面直接访问和 [xxx] 动态访问。当然了你说是误用也是有可能的,我不知道 ts 设计区分这二者是什么目的。 |