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

卧槽了 TypeScript 真不是一般人能完全驾驭的,太微妙了,这里的写法搞不懂有什么不对的

  •  
  •   makelove · 2021-10-17 12:44:48 +08:00 · 6670 次点击
    这是一个创建于 1169 天前的主题,其中的信息可能已经有所发展或是发生改变。

    写了一个复杂的函数定义,怎么搞里面的类型都是 unknown,我把问题部分最简化成这样:

    Playground 链接

    declare function create<A>(def: {
      a: () => A
      b: (a: A) => void
    }): A
    
    let s = create({
        a: () => { return { result: () => 'ok' } },
        b: (a) => {},
    })
    
    let s2 = create({
        a: () => { return { result() { return 'ok' } } },
        b: (a) => {},
    })
    

    你看这个 s 和 s2 定义是几乎一样的吧,一个出来是正常的类型 { result: () => "ok"; } 另一个是unknown。 谁知道这里到底有什么微妙的东西的里面?

    js 怎么搞就这么点东西没有不明明白白的,ts 有时候真抓狂。

    46 条回复    2022-09-01 22:20:07 +08:00
    akinoniku
        1
    akinoniku  
       2021-10-17 12:54:26 +08:00
    s 和 s2 在 js 不是等价的
    makelove
        2
    makelove  
    OP
       2021-10-17 13:01:18 +08:00
    @akinoniku 是,就差了个 this 处理。
    我这里需要传入一个函数集,所以我这里只能写个注解让别人传入函数集时要用 => 写法不能用另一种?这有点太搞笑了
    codehz
        3
    codehz  
       2021-10-17 13:12:35 +08:00
    s2 改成这样

    let s2 = create({
    a: () => { return { result() { return 'ok' } } as const },
    b: (a) => {},
    })
    makelove
        4
    makelove  
    OP
       2021-10-17 13:17:39 +08:00
    @codehz 为何 s2 加 const 就可以了呢,和 s 可以不加差在哪呢
    hronro
        5
    hronro  
       2021-10-17 13:18:47 +08:00
    感觉有点像是 TypeScript 本身的 BUG,建议你去 TypeScript 报个 issue
    makelove
        6
    makelove  
    OP
       2021-10-17 13:31:40 +08:00
    @hronro 这就不给别人添乱了,这个不可能会是未知 BUG,这么常用的东西即使是 BUG 也有大把人在提了,毕竟 ts 这流行度摆在那
    gynantim
        7
    gynantim  
       2021-10-17 13:47:13 +08:00
    不是 bug 吧。因为 s 能够通过静态分析得到类型。s2 不能。
    hronro
        8
    hronro  
       2021-10-17 13:58:56 +08:00
    @makelove #6
    我好心帮你看问题,为什么说我添堵?

    如果一个 Object literal 直接申明能推断出类型,inline 到某个对象里就不能推断出类型,这为什么不可能是 TS 的 BUG?

    makelove
        9
    makelove  
    OP
       2021-10-17 14:00:58 +08:00
    @gynantim 请问:

    declare function create<S, M, A>(def: {
    data: S
    a: (data: S) => M
    b: (m: M) => void
    }): M

    const s = create({
    data: 0,
    a: (data) => 100,
    b: (m) => {}
    })


    这种情况,为何 s 也是 unknown 呢
    感觉这是 ts 的某种特点,不过不知道在哪能有说明
    makelove
        10
    makelove  
    OP
       2021-10-17 14:06:25 +08:00
    @hronro 怎么说呢,我不是说这不一定不是个 bug,而是说即使是 bug typescript 的 issue 列表里也肯定有了。(以前我初学 ts 时就提过几个百思不得其解的 bug,后来发现都是别人提烂的,typescript 几万个 issue 不是盖的,你能想到的几乎都有,我想不是资深 ts 人士提不出新 bug
    包括这个,因为不好找关键词所以我没找到相关 bug
    xarthur
        11
    xarthur  
       2021-10-17 14:13:42 +08:00   ❤️ 1
    首先 S1 和 S2 **应该**返回的类型是不一样的。
    S1 返回的是 { result: () => string; },是个对象,里面有个 result 的属性,这个属性的类型是 () => string
    S2 返回的是 { result() : string },是个对象,这个对象里有个 result() 的方法,这个方法的返回值应该是 string
    但是在你的写法中你是没法判断{ return { result() { return 'ok' } } }这句话是要返回一个对象还是一个 闭包 /代码块 的,类型推断只能出 unknown 。
    我是这么理解的。
    xarthur
        12
    xarthur  
       2021-10-17 14:17:28 +08:00
    如果你要写一个方法集合的话,应该用第一种写法,第二种写法是错的。
    makelove
        13
    makelove  
    OP
       2021-10-17 14:17:38 +08:00
    @xarthur 应该不是这个问题,这个很明显是对象。
    你看我最后回复给 gynantim 里,那里没有对象了是个数字,结果也是 unknown
    xarthur
        14
    xarthur  
       2021-10-17 14:22:37 +08:00
    @makelove 第二个例子确实很奇怪,100 应该会自动推断成 number🤔
    hronro
        15
    hronro  
       2021-10-17 14:28:36 +08:00
    @makelove #10
    所以我才建议在 issue 列表里搜不到就直接去开新 issue,如果之前真的有人提过但你没搜到,会有人帮你标 duplicated 的。我过去几年给 TS 提过几个 issue,确实有一半是 duplicated 的,不过另外一半是确实是 TS 本身的问题
    xarthur
        16
    xarthur  
       2021-10-17 14:34:00 +08:00
    @makelove 我现在倾向于 TS 出于某种可能会出现递归类型推断计算的可能性,直接暂停了这部分的类型推断,所以出现了 unknown 。
    也或许这个是 TS 的 bug
    mx8Y3o5w3M70LC4y
        17
    mx8Y3o5w3M70LC4y  
       2021-10-17 14:34:53 +08:00 via Android
    @xarthur 第二个例子,你 b 在 declare 的时候返回值是 void,但是你调用的时候 b 的返回值类型是{},这代表一个空对象,并不是 void 。所以 ts 这时候不能推断出 s 是什么类型。。。
    mx8Y3o5w3M70LC4y
        18
    mx8Y3o5w3M70LC4y  
       2021-10-17 14:35:24 +08:00 via Android
    @lvdb 抱歉回复错了。。 @xarthur @makelove
    makelove
        19
    makelove  
    OP
       2021-10-17 14:39:26 +08:00
    @lvdb 不是,js 语法这里的空{}不是返回空对象而是不返回东西,实际返回就是 void,或者你在里面写 return undefined as void 也是一个效果
    mx8Y3o5w3M70LC4y
        20
    mx8Y3o5w3M70LC4y  
       2021-10-17 14:58:13 +08:00 via Android
    @makelove 建议你在 playground 里 console.log(typeof {}),看一下 log
    makelove
        21
    makelove  
    OP
       2021-10-17 15:15:35 +08:00
    @xarthur 第二个例子,把参数从对象形式写成位置参数就可以正确得到 number,救命啊,这是为啥,把我彻底整不会了

    declare function create<S, M, A>(
    data: S,
    a: (data: S) => M,
    b: (m: M) => void
    ): M

    const store2 = create(0, (data) => 1, (m) => {})
    noe132
        22
    noe132  
       2021-10-17 15:25:11 +08:00
    我的直觉是和 contravariance inference 有关,导致 b 覆盖了 a 的类型推导。不过我也不确定是不是一个 bug 。

    declare function create<
    Data,
    A extends (d: Data) => unknown,
    M = ReturnType<A>,
    >(def: {
    data: Data
    a: A
    b: (m: M) => void
    }): [M, ReturnType<A>]


    const s = create({
    data: 0,
    a: (data) => 100,
    b: (m) => {},
    })

    declare function create2<
    Data,
    A extends (d: Data) => unknown,
    M = ReturnType<A>,
    >(
    data: Data,
    a: A,
    b: (m: M) => void,
    ): [M, ReturnType<A>]


    const s2 = create2(
    0,
    (data) => 100,
    (m) => {},
    )

    这里在 create 中,M 和 ReturnType<A> 其实是 1 个 Type,但是 M 被用到 b 的参数后,就变成了 unknown
    而在 create2 中,所有的参数都是一样的,只是把对象拆成了 3 个参数,此时 M 的类型被正确推导成了 number

    https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXzDigxAB4AoeeAEWKgBpL4BBeEADxNWAGd4AKYAC4adAJTwAvAD54aANaocAd1SMqAWSnwASiAzIYqACoBPAA5lm0xtMEhEIgN5NgdEbQxQmUEcyYARiL8ALYiGhIy8ABuOFjA5AC+YiIA2hr0uvqGJhZW0gC65MVgeDwY8HySBEQk-C5Ubl4iAAzq8L4CTVCRsgCMLW2BwSG98E6JjMnFoJCwCCjo2Hg1IMQgAEwUVJ4MTKwcXLxdHuJSsgpKqu1a1XoGRmaWpNa2-K7uol7tncztQQIwvAIucYnFgIwUvB0pl7jknvkiiUyhUeBttIQ1iQNu8qEMqIIzlEBvjAWMJpDyEA
    FrankFang128
        23
    FrankFang128  
       2021-10-17 15:44:13 +08:00   ❤️ 10
    因为 () => 'ok' 没有 this,所以类型是明确的;
    但 () { return 'ok' } 有 this,你没有指定 this 的类型,所以类型是不明确的;
    TS 的推测规则是,只要有任何一点不明确,就不推测。
    所以你需改成 return { result(this:any) { return 'ok' } } 就行了
    FrankFang128
        25
    FrankFang128  
       2021-10-17 15:45:34 +08:00
    所以说 JS 的 this 是真的难
    makelove
        26
    makelove  
    OP
       2021-10-17 15:48:44 +08:00
    @noe132 确实离谱

    declare function create<S, M, A>(def: { data: S, a: (data: S) => M, b: (m: M) => void }): M
    const s = create({ data: 0, a: (data) => 100, b: (m) => {}})

    declare function create2<M, A>(def: { a: () => M, b: (m: M) => void }): M
    const s2 = create2({ a: () => 100, b: (m) => {}})

    这里 s 是 unknown, s2 是 number,不知道 data 影响了啥
    makelove
        27
    makelove  
    OP
       2021-10-17 15:51:57 +08:00
    @FrankFang128 你看后面的例子用数字了没有 this,我不知道这二个是不是同一个问题
    FrankFang128
        28
    FrankFang128  
       2021-10-17 15:53:27 +08:00
    @makelove 原理看 23 楼:TS 的推测规则是,只要有任何一点不明确,就不推测。
    你需要给 data 一个类型,就算是 any 都行
    FrankFang128
        29
    FrankFang128  
       2021-10-17 15:54:27 +08:00
    总之就一句话:TS 的推测规则是,只要有任何一点不明确,就不推测。
    cxk0
        30
    cxk0  
       2021-10-17 19:46:39 +08:00
    所以 V2 都是前端大佬,我 Java 后端在这里格格不入
    leafre
        31
    leafre  
       2021-10-17 20:35:15 +08:00
    s1 s2 返回类型 不一样
    zbinlin
        32
    zbinlin  
       2021-10-18 00:41:18 +08:00
    你这里 a 返回值跟 b 里的参数都需要推断,如果没有在 a 返回值或 b 的参数中推断出一个明确的类型,就无法确定类型。
    你可以在 a 里定义下 this 或者将 b 里的 a 参数明确定义成出来(可以是 any 或 unknown )。

    同理,你在楼层里举的例子也是一样的问题。
    coolmenu
        33
    coolmenu  
       2021-10-18 07:12:10 +08:00
    看的脑仁疼,ts 这么复杂吗?
    sunwang
        34
    sunwang  
       2021-10-18 09:27:45 +08:00
    不知道楼主的 ts 是什么版本,我的 ts 是可以推断的,版本是 3.7.2
    lscho
        35
    lscho  
       2021-10-18 10:09:45 +08:00
    这明显 s 和 s2 不一样啊,楼主为什么会得出 s 和 s2 定义是几乎一样。匿名函数没有 this 。
    makelove
        36
    makelove  
    OP
       2021-10-18 11:14:34 +08:00
    @lscho 那你解释一下为什么 this 会影响这个推断,以及下面的例子用数字也不能推断。
    而把参数从对象形式改成位置形式就完全可以推断了


    @sunwang playground 网页上可以选版本,3.7 也不行
    makelove
        37
    makelove  
    OP
       2021-10-18 11:16:08 +08:00
    @zbinlin 你是说 ts 二个类型不能同时推?那为什么改成位置参数就可以同时推呢?
    SmiteChow
        38
    SmiteChow  
       2021-10-18 11:30:10 +08:00
    讲句实话,写 ts 大部分人遇到复杂对象类型注解都写 any,实践里也没毛病。
    thtznet
        39
    thtznet  
       2021-10-18 11:43:56 +08:00
    我不写 TS,只会写 JS 和 C#,传问 TS 是像 C#一样的 JS,但是看了这个问题,我表示 TS 在正宗的强类型(C#)前还是像个玩具语言,我本来打算学一下 TS 让 JS 写得舒服一点,现在有点慌,到底要不要把 JS 过渡到 TS ?既不像 C#那么严谨也丢失了 JS 的灵活和随性,这个搞得有点尴尬。
    lscho
        40
    lscho  
       2021-10-18 11:51:23 +08:00
    @makelove 我理解的和 23 楼一样。ts 是可以从上下文环境中推断类型的。() => 'ok' 这样本身没有 this,所以从外层上下文环境中可以推断。() { return 'ok' }这样单独生成了作用域,有了 this,且没有指定,所以类型不明确。
    makelove
        41
    makelove  
    OP
       2021-10-18 12:44:12 +08:00
    @thtznet C#我只会一点,java 以前写过二年,但 TS 的类型能力全面吊打 java 和 c#是没有疑问的,大量 java/c#搞不定的类型设计 ts 就可以,可能是因为 js 的灵活性所以 ts 也只能这么灵活。平时碰到疑难杂证的可能还是很少的,js 社区这么快全面拥抱 ts 不是没有道理的,编程效率提高太多了
    thtznet
        42
    thtznet  
       2021-10-18 13:03:17 +08:00
    @makelove 听你这么说,那我还是试试看
    zbinlin
        43
    zbinlin  
       2021-10-18 21:56:26 +08:00
    @makelove 哪里改变位置参数可以同时推?
    makelove
        44
    makelove  
    OP
       2021-10-19 09:49:05 +08:00
    @zbinlin 现在 a,b 是对象参数,去掉{}改成位置的。改成位置的后,怎么写复杂的都能正确推导。
    zbinlin
        45
    zbinlin  
       2021-10-19 10:03:25 +08:00
    @makelove

    declare function create<A>(a: () => A, b: (a: A) => void): A

    这样吗?这样不是分开了呀

    不过这个可能还真是个 bug 😂
    conateri
        46
    conateri  
       2022-09-01 22:20:07 +08:00   ❤️ 1


    declare function create<S, M, A>(def: {
    data: S
    a: (data: S) => M
    b: (m: M) => void
    }): M

    const s = create({
    data: 0,
    a: (data) => 100,
    b: (m) => {}
    })

    [playground]( https://www.typescriptlang.org/play?ts=4.7.4#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXzDigxAB4BlAGngFlqBBAPgApREAueAbwCh55gxKJ3J94w+KyEiAlPAC8jWmIBGnZgFtONOYvgA3HFmA8AvjO08eYPAGcM8WwoJESzXv0EYJABkpiJKW9dJQBGHz9VdQ0Q7lMzGSA)

    4.7.4 版本可以把 s 正确推断为 number, 是 bug 没错了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1364 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 17:15 · PVG 01:15 · LAX 09:15 · JFK 12:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.