卧槽了 TypeScript 真不是一般人能完全驾驭的,太微妙了,这里的写法搞不懂有什么不对的 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
makelove
V2EX    TypeScript

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

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

    写了一个复杂的函数定义,怎么搞里面的类型都是 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 这流行度摆在那
    Stop
        7
    Stop  
       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     Solana     4432 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 35ms UTC 01:08 PVG 09:08 LAX 17:08 JFK 20:08
    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