原文地址如何解决 TS2322: “could be instantiated with a different subtype of constraint” - 掘金
如何解决 TS2322 报错: “could be instantiated with a different subtype of constraint”
遇到问题
最近使用 ts 写个工具类函数时, 遇到了 ts 报错:
1 | function renameKeys<T extends { [key: string]: unknown }>( |
问题出在函数返回的泛型 T.
随后我将泛型 T
改为 { [key: string]: unknown }
, 报错消失了.
1 | function renameKeys<T extends { [key: string]: unknown }>( |
这就很奇怪, { [key: string]: unknown }
本身就为 T
的约束, 用 { [key: string]: unknown }
和用 T
有什么区别吗?
在我一顿 Google 之后, 在一篇文章中明白了其中道理.
理解 TS 报错信息
下面我将分解错误消息的每句话:
1 | Type '{}' is not assignable to type 'T'. |
Type '{}'
什么意思?
这个类型可以分配任何值,除了 null
或 undefined
。例如:
1 | type A = {}; |
is not assignable
什么意思?
分配是实例与类型相匹配。如果你的实例不匹配类型,你会得到一个错误。例如:
1 | // type string is not assignable to type number |
a different subtype
什么意思?
- A 是 S 的子类型: 类型 A 在类型 S 的基础上增加了额外属性.
- A 和 B 是 S 的不同子类型: 类型 A 与类型 B 分别在类型 S 的基础上增加了
不同的
额外属性.
例如: 下面代码的情况是
- A 和 D 是相同的类型
- B 是 A 的子类型
- E 不是 A 的子类型
- B 和 C 是 A 的不同子类型
1 | type A = { readonly 0: "0" }; |
1 | type A = number; |
1 | type A = boolean; |
当你在 ts 中使用 type 关键字时, 例如:
type A = { foo: 'Bar' }
, 那么 A 指向的是该值的结构.
constraint of type 'T'
什么意思?
类型约束仅仅是你放在 extends
关键字右侧的内容。在下面的例子中,类型约束是’B’。
1 | const func = <A extends B>(a: A) => `hello!`; |
所以, Type ‘B’ is the constraint of type ‘A’.
类型约束 extends
为了说明这一点,我将展示三种情况。在每种情况下唯一会变化的是类型约束,其他什么都不会改变。
我想让你注意的是,类型约束不会限制其子类型。看以下示例:
Given:
1 | type Foo = { readonly 0: "0" }; |
CASE 1: 无类型约束
1 | const func = <A>(a: A) => `hello!`; |
CASE 2: 一般的类型约束
在 Typescript 中,类型约束不会限制其子类型.
1 | const func = <A extends Foo>(a: A) => `hello!`; |
CASE 3: 更具体的约束
1 | const func = <A extends SubType>(a: A) => `hello!`; |
总结示例
以下函数:
1 | const func = <A extends Foo>(a: A = foo_SubType) => `hello!`; //error! |
产生如下错误信息:
1 | Type 'SubType' is not assignable to type 'A'. |
因为 Typescript 是从函数调用中
推断出 A,并且在语言中并没有限制你用不同的 ‘Foo’ 子类型来调用函数。例如,下面的所有函数调用都被认为是有效的:
1 | const c0 = func(foo); // ok! type 'Foo' will be infered and assigned to 'A' |
因此,将具体类型赋值给泛型类型形参是不正确的,因为在 TS 中,类型形参总是可以实例化为任意不同的子类型。
结论: 永远不要将具体类型赋给泛型类型参数,将其视为只读类型!
相反, 这样做:
1 | const func = <A extends Foo>(a: A) => `hello!`; //ok! |
结论
- 泛型是函数运行时推断出的类型;
- 不要给泛型类型的形参设置默认值;
- 若非设置默认值不可, 只能断言泛型 😞