
写了一个复杂的函数定义,怎么搞里面的类型都是 unknown,我把问题部分最简化成这样:
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 有时候真抓狂。
1 akinoniku 2021-10-17 12:54:26 +08:00 s 和 s2 在 js 不是等价的 |
2 makelove OP @akinoniku 是,就差了个 this 处理。 我这里需要传入一个函数集,所以我这里只能写个注解让别人传入函数集时要用 => 写法不能用另一种?这有点太搞笑了 |
3 codehz 2021-10-17 13:12:35 +08:00 s2 改成这样 let s2 = create({ a: () => { return { result() { return 'ok' } } as const }, b: (a) => {}, }) |
5 hronro 2021-10-17 13:18:47 +08:00 感觉有点像是 TypeScript 本身的 BUG,建议你去 TypeScript 报个 issue |
6 makelove OP @hronro 这就不给别人添乱了,这个不可能会是未知 BUG,这么常用的东西即使是 BUG 也有大把人在提了,毕竟 ts 这流行度摆在那 |
7 Stop 2021-10-17 13:47:13 +08:00 不是 bug 吧。因为 s 能够通过静态分析得到类型。s2 不能。 |
8 hronro 2021-10-17 13:58:56 +08:00 @makelove #6 我好心帮你看问题,为什么说我添堵? 如果一个 Object literal 直接申明能推断出类型,inline 到某个对象里就不能推断出类型,这为什么不可能是 TS 的 BUG? ![]() |
9 makelove OP @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 的某种特点,不过不知道在哪能有说明 |
10 makelove OP @hronro 怎么说呢,我不是说这不一定不是个 bug,而是说即使是 bug typescript 的 issue 列表里也肯定有了。(以前我初学 ts 时就提过几个百思不得其解的 bug,后来发现都是别人提烂的,typescript 几万个 issue 不是盖的,你能想到的几乎都有,我想不是资深 ts 人士提不出新 bug 包括这个,因为不好找关键词所以我没找到相关 bug |
11 xarthur 2021-10-17 14:13:42 +08:00 首先 S1 和 S2 **应该**返回的类型是不一样的。 S1 返回的是 { result: () => string; },是个对象,里面有个 result 的属性,这个属性的类型是 () => string S2 返回的是 { result() : string },是个对象,这个对象里有个 result() 的方法,这个方法的返回值应该是 string 但是在你的写法中你是没法判断{ return { result() { return 'ok' } } }这句话是要返回一个对象还是一个 闭包 /代码块 的,类型推断只能出 unknown 。 我是这么理解的。 |
12 xarthur 2021-10-17 14:17:28 +08:00 如果你要写一个方法集合的话,应该用第一种写法,第二种写法是错的。 |
13 makelove OP @xarthur 应该不是这个问题,这个很明显是对象。 你看我最后回复给 gynantim 里,那里没有对象了是个数字,结果也是 unknown |
15 hronro 2021-10-17 14:28:36 +08:00 @makelove #10 所以我才建议在 issue 列表里搜不到就直接去开新 issue,如果之前真的有人提过但你没搜到,会有人帮你标 duplicated 的。我过去几年给 TS 提过几个 issue,确实有一半是 duplicated 的,不过另外一半是确实是 TS 本身的问题 |
16 xarthur 2021-10-17 14:34:00 +08:00 @makelove 我现在倾向于 TS 出于某种可能会出现递归类型推断计算的可能性,直接暂停了这部分的类型推断,所以出现了 unknown 。 也或许这个是 TS 的 bug |
17 mx8Y3o5w3M70LC4y 2021-10-17 14:34:53 +08:00 via Android @xarthur 第二个例子,你 b 在 declare 的时候返回值是 void,但是你调用的时候 b 的返回值类型是{},这代表一个空对象,并不是 void 。所以 ts 这时候不能推断出 s 是什么类型。。。 |
18 mx8Y3o5w3M70LC4y 2021-10-17 14:35:24 +08:00 via Android |
19 makelove OP @lvdb 不是,js 语法这里的空{}不是返回空对象而是不返回东西,实际返回就是 void,或者你在里面写 return undefined as void 也是一个效果 |
20 mx8Y3o5w3M70LC4y 2021-10-17 14:58:13 +08:00 via Android @makelove 建议你在 playground 里 console.log(typeof {}),看一下 log |
21 makelove OP @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) => {}) |
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 |
23 FrankFang128 2021-10-17 15:44:13 +08:00 因为 () => 'ok' 没有 this,所以类型是明确的; 但 () { return 'ok' } 有 this,你没有指定 this 的类型,所以类型是不明确的; TS 的推测规则是,只要有任何一点不明确,就不推测。 所以你需改成 return { result(this:any) { return 'ok' } } 就行了 |
24 FrankFang128 2021-10-17 15:44:44 +08:00 |
25 FrankFang128 2021-10-17 15:45:34 +08:00 所以说 JS 的 this 是真的难 |
26 makelove OP @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 影响了啥 |
27 makelove OP @FrankFang128 你看后面的例子用数字了没有 this,我不知道这二个是不是同一个问题 |
28 FrankFang128 2021-10-17 15:53:27 +08:00 @makelove 原理看 23 楼:TS 的推测规则是,只要有任何一点不明确,就不推测。 你需要给 data 一个类型,就算是 any 都行 |
29 FrankFang128 2021-10-17 15:54:27 +08:00 总之就一句话:TS 的推测规则是,只要有任何一点不明确,就不推测。 |
30 cxk0 2021-10-17 19:46:39 +08:00 所以 V2 都是前端大佬,我 Java 后端在这里格格不入 |
31 leafre 2021-10-17 20:35:15 +08:00 s1 s2 返回类型 不一样 |
32 zbinlin 2021-10-18 00:41:18 +08:00 你这里 a 返回值跟 b 里的参数都需要推断,如果没有在 a 返回值或 b 的参数中推断出一个明确的类型,就无法确定类型。 你可以在 a 里定义下 this 或者将 b 里的 a 参数明确定义成出来(可以是 any 或 unknown )。 同理,你在楼层里举的例子也是一样的问题。 |
33 coolmenu 2021-10-18 07:12:10 +08:00 看的脑仁疼,ts 这么复杂吗? |
34 sunwang 2021-10-18 09:27:45 +08:00 不知道楼主的 ts 是什么版本,我的 ts 是可以推断的,版本是 3.7.2 |
35 lscho 2021-10-18 10:09:45 +08:00 这明显 s 和 s2 不一样啊,楼主为什么会得出 s 和 s2 定义是几乎一样。匿名函数没有 this 。 |
36 makelove OP |
38 SmiteChow 2021-10-18 11:30:10 +08:00 讲句实话,写 ts 大部分人遇到复杂对象类型注解都写 any,实践里也没毛病。 |
39 thtznet 2021-10-18 11:43:56 +08:00 我不写 TS,只会写 JS 和 C#,传问 TS 是像 C#一样的 JS,但是看了这个问题,我表示 TS 在正宗的强类型(C#)前还是像个玩具语言,我本来打算学一下 TS 让 JS 写得舒服一点,现在有点慌,到底要不要把 JS 过渡到 TS ?既不像 C#那么严谨也丢失了 JS 的灵活和随性,这个搞得有点尴尬。 |
40 lscho 2021-10-18 11:51:23 +08:00 @makelove 我理解的和 23 楼一样。ts 是可以从上下文环境中推断类型的。() => 'ok' 这样本身没有 this,所以从外层上下文环境中可以推断。() { return 'ok' }这样单独生成了作用域,有了 this,且没有指定,所以类型不明确。 |
41 makelove OP @thtznet C#我只会一点,java 以前写过二年,但 TS 的类型能力全面吊打 java 和 c#是没有疑问的,大量 java/c#搞不定的类型设计 ts 就可以,可能是因为 js 的灵活性所以 ts 也只能这么灵活。平时碰到疑难杂证的可能还是很少的,js 社区这么快全面拥抱 ts 不是没有道理的,编程效率提高太多了 |
45 zbinlin 2021-10-19 10:03:25 +08:00 |
46 conateri 2022-09-01 22:20:07 +08:00 挖 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 没错了 |