V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
june4
V2EX  ›  TypeScript

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

  •  
  •   june4 · 31 天前 · 1490 次点击
    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 条回复    2024-09-23 12:18:07 +08:00
    nno
        1
    nno  
       31 天前
    gpt 的回答
    [yyy${s}]: 3 不报错原因:

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

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

    那为啥 JAVA 那边好像没什么人讨论类型?
    june4
        14
    june4  
    OP
       29 天前
    @lizy0329 ts 支持编译时字符串模板的,这个用法用在任何地方都行,就是用在这里的对象键上不行。
    java 的类型就算了,灵活度很低,完全没有类型体操的余地,不可能有看不懂的地方。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3089 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 00:37 · PVG 08:37 · LAX 17:37 · JFK 20:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.