TypeScript 能不能别这么古怪,这行为什么会报错呢? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
june4
V2EX    TypeScript

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

  •  
  •   june4 2024 年 9 月 21 日 3822 次点击
    这是一个创建于 511 天前的主题,其中的信息可能已经有所发展或是发生改变。
    type Obj = { xxxFoo?: number ,xxxBar?: number } declare function func(obj: Obj): void type Suffix = 'Foo' const s: Suffi = 'Foo' func({ [`xxx${'Bar'}`]: 2, [`yyy${'Foo'}`]: 2, // 这行会报错,符合预期 [`yyy${s}`]: 3, // 但这行为什么不会报错???!!! [`xxx${s}` satisfies keyof Obj]: 4 }) 

    为什么那行不会报错,和上一行到底有什么区别啊?我也是服了,所有上火的地方都上在 TypeScript 上。

    14 条回复    2024-09-23 12:18:07 +08:00
    nno
        1
    nno  
       2024 年 9 月 21 日
    gpt 的回答
    [yyy${s}]: 3 不报错原因:

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

    另外加一句,一定非要写这么奇怪的代码吗?
    june4
        7
    june4  
    OP
       2024 年 9 月 21 日
    @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
        8
    june4  
    OP
       2024 年 9 月 21 日
    @xiangyuecn 这个代码不奇怪吧,业务上明明很多地方用到的。这些代码是我出错后简化而来的。
    实际上,是我一个配置对象, { xxxOnMobile, xxxOnDesktop, yyyOnMobile, yyyOnDesktop ... }
    这里 'OnMobile' | 'OnDesktop' 是后缀,这套 Template Literal 展开用于别的变量和参数上可以,但用在对象键上就不行了,我也很奇怪啊。
    AV1
        9
    AV1  
       2024 年 9 月 21 日   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 } //虽然多了个属性,但它不是字面量,所以不报错
    june4
        10
    june4  
    OP
       2024 年 9 月 21 日
    @DOLLOR 你这个 Math.random 是运行时信息,当然无法报错了。但我那边明明是编译期可以确定的值。
    lisongeee
        11
    lisongeee  
       2024 年 9 月 21 日
    因为 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
        12
    zbinlin  
       2024 年 9 月 21 日
    还是 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
        13
    lizy0329  
       2024 年 9 月 23 日
    @june4
    总的来说就是如果不是字面量,都需要使用一些额外的规则工具来约束

    那为啥 JAVA 那边好像没什么人讨论类型?
    june4
        14
    june4  
    OP
       2024 年 9 月 23 日
    @lizy0329 ts 支持编译时字符串模板的,这个用法用在任何地方都行,就是用在这里的对象键上不行。
    java 的类型就算了,灵活度很低,完全没有类型体操的余地,不可能有看不懂的地方。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1887 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 06:26 PVG 14:26 LAX 22:26 JFK 01:26
    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