补充@fetzz的优秀回答。
简短回答
TLDR; 这种错误信息有两个常见的原因。你正在做第一个(见下文)。除了文字之外,我会详细解释这个错误信息想要传达的内容。
原因 1: 在 TypeScript 中,不允许将具体实例分配给类型参数。以下是一个“问题”和“问题解决”的示例,您可以比较差异并查看更改:
问题
const func1 = <A extends string>(a: A = 'foo') => `hello!`
const func2 = <A extends string>(a: A) => {
a = `foo`
}
解决方案
const func1 = <A extends string>(a: A) => `hello!`
const func2 = <A extends string>(a: A) => {
}
在 TS Playground 中查看
原因2:虽然您的代码中没有以下错误,但这种错误消息弹出的情况也很常见。您应该避免做以下操作:
在类、类型或接口中重复(错误地)使用类型参数。
不要让下面代码的复杂性困扰你,我只希望你集中精力看如何通过去除字母'A'来解决问题:
问题:
type Foo<A> = {
map: <A,B>(f: (_: A) => B) => Foo<B>
}
const makeFoo = <A>(a: A): Foo<A> => ({
map: f => makeFoo(f(a))
})
解决方案:
type Foo<A> = {
map: <B>(f: (_: A) => B) => Foo<B>
}
const makeFoo = <A>(a: A): Foo<A> => ({
map: f => makeFoo(f(a))
})
在TS Playground中查看:
长篇回答
了解错误信息
接下来,我将分解错误消息的每个元素:
Type '{}' is not assignable to type 'P'.
'{}' is assignable to the constraint of type 'P', but 'P' could be
instantiated with a different subtype of constraint'object'
{}是什么类型
{}
是一种类型,你可以分配除了null或undefined以外的任何东西。例如:
type A = {}
const a0: A = undefined
const a1: A = null
const a2: A = 2
const a3: A = 'hello world'
const a4: A = { foo: 'bar' }
查看:TS Playground
什么是“is not assignable”
赋值是将特定类型的变量对应于特定实例。如果实例的类型不匹配,则会出现错误。例如:
const a: number = 'hello world'
const b: number = 2
什么是不同子类型
两种类型相等:如果它们之间没有添加或删除细节。
两种类型不同:如果它们不相等。
类型A
是类型S
的子类型:如果A
添加细节,而不是从S
中删除已存在的细节。
类型A
和类型B
是类型S
的不同子类型:如果A
和B
都是S
的子类型,但A
和B
是不同的类型。换句话说:A
和B
为类型S
添加了细节,但它们没有添加相同的细节。
例如:在下面的代码中,所有以下语句都是正确的:
- A和D是相等的类型
- B是A的子类型
- E不是A的子类型
- B和C是A的不同子类型
type A = { readonly 0: '0'}
type B = { readonly 0: '0', readonly foo: 'foo'}
type C = { readonly 0: '0', readonly bar: 'bar'}
type D = { readonly 0: '0'}
type E = { readonly 1: '1', readonly bar: 'bar'}
type A = number
type B = 2
type C = 7
type D = number
type E = `hello world`
type A = boolean
type B = true
type C = false
type D = boolean
type E = number
注意:
结构类型
当你在TS中看到使用type关键字,例如在
type A = { foo: 'Bar' }
中,你应该理解为:类型别名
A
指向类型结构
{ foo: 'Bar' }
。
一般语法是:
type [type_alias_name] = [type_structure]
。
Typescript类型系统仅检查
[type_structure]
,而不检查
[type_alias_name]
。这意味着在TS中,在类型检查方面以下两种情况没有区别:
type A = { foo: 'bar' }
和
type B = { foo: 'bar' }
。更多信息请参见:
官方文档。
什么是类型约束'X'
类型约束是你放在“extends”关键字右侧的内容。在下面的示例中,类型约束
为'B'。
const func = <A extends B>(a: A) => `hello!`
阅读:类型约束“B”是类型“A”的约束
为什么会出现错误
为了说明问题,我将展示三种情况。每种情况唯一变化的是Type Constraint
,其他都不会改变。
我希望你注意到Type Constraint
对Type Parameter
的限制不包括不同的子类型。让我们来看看:
假设:
type Foo = { readonly 0: '0'}
type SubType = { readonly 0: '0', readonly a: 'a'}
type DiffSubType = { readonly 0: '0', readonly b: 'b'}
const foo: Foo = { 0: '0'}
const foo_SubType: SubType = { 0: '0', a: 'a' }
const foo_DiffSubType: DiffSubType = { 0: '0', b: 'b' }
CASE 1:
无限制
const func = <A>(a: A) => `hello!`
const c0 = func(undefined)
const c1 = func(null)
const c2 = func(() => undefined)
const c3 = func(10)
const c4 = func(`hi`)
const c5 = func({})
const c6 = func(foo)
const c7 = func(foo_SubType)
const c8 = func(foo_DiffSubType)
案例2: 某些限制
请注意以下限制不会影响子类型。
非常重要:在Typescript中,类型约束
不会限制不同的子类型
const func = <A extends Foo>(a: A) => `hello!`
const c0 = func(undefined)
const c1 = func(null)
const c2 = func(() => undefined)
const c3 = func(10)
const c4 = func(`hi`)
const c5 = func({})
const c6 = func(foo)
const c7 = func(foo_SubType)
const c8 = func(foo_DiffSubType)
案例3:更加受限制
const func = <A extends SubType>(a: A) => `hello!`
const c0 = func(undefined)
const c1 = func(null)
const c2 = func(() => undefined)
const c3 = func(10)
const c4 = func(`hi`)
const c5 = func({})
const c6 = func(foo)
const c7 = func(foo_SubType)
const c8 = func(foo_DiffSubType)
请在TS playground中查看
结论
以下函数:
const func = <A extends Foo>(a: A = foo_SubType) => `hello!`
会产生以下错误信息:
Type 'SubType' is not assignable to type 'A'.
'SubType' is assignable to the constraint of type 'A', but 'A'
could be instantiated with a different subtype of constraint
'Foo'.ts(2322)
因为Typescript从函数调用中推断出A
,但语言没有限制您使用不同的“Foo”子类型调用函数。例如,以下所有函数调用都被视为有效:
const c0 = func(foo)
const c1 = func(foo_SubType)
const c2 = func(foo_DiffSubType)
因此,将具体类型分配给泛型{{Type Parameter}}是不正确的,因为在TS中,{{Type Parameter}}始终可以实例化为某个任意不同的子类型。
解决方案:
永远不要将具体类型分配给泛型类型参数,将其视为只读!而是这样做:
const func = <A extends Foo>(a: A) => `hello!`
请在TS Playground中查看