type Obj = { xxxFoo?: number ,xxxBar?: number }
declare function func(obj: Obj): void
type Suffix = 'Foo'
const s: Suffix = 'Foo'
func({
[`xxx${'Bar'}`]: 2,
[`yyy${'Foo'}`]: 2, // 这行会报错,符合预期
[`yyy${s}`]: 3, // 但这行为什么不会报错???!!!
[`xxx${s}` satisfies keyof Obj]: 4
})
为什么那行不会报错,和上一行到底有什么区别啊?我也是服了,所有上火的地方都上在 TypeScript 上。
1
nno 67 天前
gpt 的回答
[yyy${s}]: 3 不报错原因: 在这里,TypeScript 看到的是一个动态计算的键名 yyy${s}。尽管 s 的值是 'Foo',它不会直接解析为 yyyFoo 。TypeScript 只知道这是一个字符串键,没有对该键进行进一步的类型检查,因此不会报错。 |
2
galikeoy 67 天前
确实,虽然 v2 不能贴 ai 的回答,这贴应该可以吧~
通义灵码和腾讯 ai 代码的回答: ['yyy${"Foo"}'] 报错是因为 "Foo" 被直接拼接成字符串,导致键名不是类型 Obj 中已知的键名。 ['yyy${s}'] 不报错是因为 s 是类型 Suffix 的实例,TypeScript 会尝试推断 s 的值,从而认为它是安全的。 |
3
june4 OP @nno @galikeoy 感觉没这么简单
type Obj = { xxxFoo?: number ,xxxBar?: number } type Suffix = 'Foo' | 'Bar' function func(_obj: Obj): void {} const s: () => Suffix = () => 'Foo' const s2 = s() const s3: Suffix = 'Foo' // 这里,s2 和 s3 都是 Suffix 类型,那 func({ [`yyy${s2}` as const]: 3 }) func({ [`yyy${s3}` as const]: 3 }) // 为什么这行会报错而上一行不会呢? |
4
june4 OP const s: () => Suffix = () => 'Foo'
const s2 = s() const s3: Suffix = 'Foo' const s4: Suffix = s2 func({ [`yyy${s2}` as const]: 3 }) func({ [`yyy${s3}` as const]: 3 }) // 就这行报错 func({ [`yyy${s4}` as const]: 3 }) 为啥 s3 和 s4 的声明都一样类型,但一个报错一个不错,明面上的类型信息不代表全部还有个隐藏的部分? |
5
civetcat 67 天前
我试了一下,如果 s 变量不强制指定为 Suffix,让它自动推断是可以的。但是指定了 s 的类型,就无法推断成功了/。如果显示指定类型,ts 能判断出来一个不可变的字符串字面量类型,但是显示指定了 type 类型,导致 s 变成一个类型,但是这个类型是可变的?比如重新定义 Suffix 的类型为其他类型,可能是处于这种情况导致无法进一步推断?
|
6
xiangyuecn 67 天前
"yyyFoo" //编译时检查?
"yyy"+s //编译后的 js 狗都不理 ts 类型? 另外加一句,一定非要写这么奇怪的代码吗? |
7
june4 OP @civetcat typescript 对参数明明有这个能力啊?
type Suffix2 = 'Foo' | 'Bar' let sx: Suffix2 = 'Foo' let sxf: () => Suffix2 = () => 'Foo' function key(a: 'xxxFoo' | 'xxxBar') {} key(`xxx${sx}`) key(`yyy${sx}`) // 这行错,符合预期 key(`xxx${sxf()}`) key(`yyy${sxf()}`) // 这行错,符合预期 这个能力放到对象键上就失效了? |
8
june4 OP @xiangyuecn 这个代码不奇怪吧,业务上明明很多地方用到的。这些代码是我出错后简化而来的。
实际上,是我一个配置对象, { xxxOnMobile, xxxOnDesktop, yyyOnMobile, yyyOnDesktop ... } 这里 'OnMobile' | 'OnDesktop' 是后缀,这套 Template Literal 展开用于别的变量和参数上可以,但用在对象键上就不行了,我也很奇怪啊。 |
9
DOLLOR 67 天前 1
TS 检查对象属性的时候,只会对多余的*字面量*( literal )属性报错。对于*非字面量*对象,以及*非字面量*属性,会按照*协变*规则判断类型是否符合规则。
原问题里的`yyy${s}`属于计算属性,它就相当于一个*多出来*的属性,并且不是字面量,所以不会报错。 let a: { a: number } = { a: 1 } a = { a: 1, b: 2 } //字面量多了个 b ,报错 let ab: { a: number, b: number } = { a: 1, b: 2 } a = ab //非字面量赋值,虽然多了个 b ,但符合协变规则,不报错 a = { a: 1, [Math.random()]: 6 } //虽然多了个属性,但它不是字面量,所以不报错 |
11
lisongeee 67 天前
因为 typescript 没你想的这么智能
let x: number | undefined = undefined; const run = (cb: Function) => cb(); run(() => (x = 1)); const y: typeof x = 1; // Type '1' is not assignable to type 'undefined'. 另外建议没必要过于纠结 typescript 的类型体操,比如我会尽量避免复杂类型,能用 interface 就不用 type 当然你要用特性什么想用就是了,如果觉得不好用,完全可以 fork 自己改一份,虽然大多数人没那能力 |
12
zbinlin 67 天前
还是 Typescript 类型推导还不够强大 /🐶
你如果声明一个这样的变量: ``` const obj = { [`yyy${s}`]: 10, }; ``` 你会发现 typescript 推导成: ``` { [x: string]: number; } ``` 类型,这就解释了为什么这里 `yyy${s}` 不会报错。 你也可以像第四行那样,加上 `satisfies` 操作符来验证。 ``` const obj = { [`yyy${s}` satisfies keyof Obj]: 10, }; ``` 当然,可以试试去 Github 的 Typescript 项目里问问,说不准可能是个 bug 。:) |