TypeScript 能不能别这么古怪,这行为什么会报错呢?

31 天前
 june4
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 上。

1490 次点击
所在节点    TypeScript
14 条回复
nno
31 天前
gpt 的回答
[yyy${s}]: 3 不报错原因:

在这里,TypeScript 看到的是一个动态计算的键名 yyy${s}。尽管 s 的值是 'Foo',它不会直接解析为 yyyFoo 。TypeScript 只知道这是一个字符串键,没有对该键进行进一步的类型检查,因此不会报错。
galikeoy
31 天前
确实,虽然 v2 不能贴 ai 的回答,这贴应该可以吧~
通义灵码和腾讯 ai 代码的回答:
['yyy${"Foo"}'] 报错是因为 "Foo" 被直接拼接成字符串,导致键名不是类型 Obj 中已知的键名。
['yyy${s}'] 不报错是因为 s 是类型 Suffix 的实例,TypeScript 会尝试推断 s 的值,从而认为它是安全的。
june4
31 天前
@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 }) // 为什么这行会报错而上一行不会呢?
june4
31 天前
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 的声明都一样类型,但一个报错一个不错,明面上的类型信息不代表全部还有个隐藏的部分?
civetcat
31 天前
我试了一下,如果 s 变量不强制指定为 Suffix,让它自动推断是可以的。但是指定了 s 的类型,就无法推断成功了/。如果显示指定类型,ts 能判断出来一个不可变的字符串字面量类型,但是显示指定了 type 类型,导致 s 变成一个类型,但是这个类型是可变的?比如重新定义 Suffix 的类型为其他类型,可能是处于这种情况导致无法进一步推断?
xiangyuecn
31 天前
"yyyFoo" //编译时检查?
"yyy"+s //编译后的 js 狗都不理 ts 类型?

另外加一句,一定非要写这么奇怪的代码吗?
june4
31 天前
@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()}`) // 这行错,符合预期

这个能力放到对象键上就失效了?
june4
31 天前
@xiangyuecn 这个代码不奇怪吧,业务上明明很多地方用到的。这些代码是我出错后简化而来的。
实际上,是我一个配置对象, { xxxOnMobile, xxxOnDesktop, yyyOnMobile, yyyOnDesktop ... }
这里 'OnMobile' | 'OnDesktop' 是后缀,这套 Template Literal 展开用于别的变量和参数上可以,但用在对象键上就不行了,我也很奇怪啊。
DOLLOR
31 天前
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 } //虽然多了个属性,但它不是字面量,所以不报错
june4
31 天前
@DOLLOR 你这个 Math.random 是运行时信息,当然无法报错了。但我那边明明是编译期可以确定的值。
lisongeee
31 天前
因为 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 自己改一份,虽然大多数人没那能力
zbinlin
31 天前
还是 Typescript 类型推导还不够强大 /🐶

你如果声明一个这样的变量:

```
const obj = {
[`yyy${s}`]: 10,
};
```

你会发现 typescript 推导成:

```
{
[x: string]: number;
}
```
类型,这就解释了为什么这里 `yyy${s}` 不会报错。

你也可以像第四行那样,加上 `satisfies` 操作符来验证。

```
const obj = {
[`yyy${s}` satisfies keyof Obj]: 10,
};
```

当然,可以试试去 Github 的 Typescript 项目里问问,说不准可能是个 bug 。:)
lizy0329
29 天前
@june4
总的来说就是如果不是字面量,都需要使用一些额外的规则工具来约束

那为啥 JAVA 那边好像没什么人讨论类型?
june4
29 天前
@lizy0329 ts 支持编译时字符串模板的,这个用法用在任何地方都行,就是用在这里的对象键上不行。
java 的类型就算了,灵活度很低,完全没有类型体操的余地,不可能有看不懂的地方。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1074632

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX