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

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

  •  
  •   june4 Sep 21, 2024 3964 views
    This topic created in 598 days ago, the information mentioned may be changed or developed.
    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 上。

    14 replies    2024-09-23 12:18:07 +08:00
    nno
        1
    nno  
       Sep 21, 2024
    gpt 的回答
    [yyy${s}]: 3 不报错原因:

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

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

    那为啥 JAVA 那边好像没什么人讨论类型?
    june4
        14
    june4  
    OP
       Sep 23, 2024
    @lizy0329 ts 支持编译时字符串模板的,这个用法用在任何地方都行,就是用在这里的对象键上不行。
    java 的类型就算了,灵活度很低,完全没有类型体操的余地,不可能有看不懂的地方。
    About     Help     Advertise     Blog     API     FAQ     Solana     1750 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 42ms UTC 16:22 PVG 00:22 LAX 09:22 JFK 12:22
    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