补充@fetzz 很好的答案。
简短回答
TLDR; 此类错误消息有两个常见原因。你正在做第一个(见下文)。除了文本,我还详细解释了此错误消息想要传达的内容。
原因 1: 在 typescript 中,不允许将具体实例分配给类型参数。下面您可以看到“问题”和“问题已解决”的示例,因此您可以比较差异并查看有哪些变化:
问题
const func1 = <A extends string>(a: A = 'foo') => `hello!` // Error!
const func2 = <A extends string>(a: A) => {
//stuff
a = `foo` // Error!
//stuff
}
解决方案
const func1 = <A extends string>(a: A) => `hello!` // ok
const func2 = <A extends string>(a: A) => { //ok
//stuff
//stuff
}
参见:TS Playground
原因 2: 虽然您没有在代码中出现以下错误。弹出这种错误消息也是正常情况。你应该避免这样做:
在类、类型或接口中重复(错误地)Type Parameter。
不要让下面代码的复杂性让您感到困惑,我想让您集中精力的唯一一件事是删除字母“A”如何解决问题:
问题:
type Foo<A> = {
//look the above 'A' is conflicting with the below 'A'
map: <A,B>(f: (_: A) => B) => Foo<B>
}
const makeFoo = <A>(a: A): Foo<A> => ({
map: f => makeFoo(f(a)) //error!
})
解决方案:
type Foo<A> = {
// conflict removed
map: <B>(f: (_: A) => B) => Foo<B>
}
const makeFoo = <A>(a: A): Foo<A> => ({
map: f => makeFoo(f(a)) //ok
})
参见: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 // error
const a1: A = null // error
const a2: A = 2 // ok
const a3: A = 'hello world' //ok
const a4: A = { foo: 'bar' } //ok
// and so on...
参见:TS Playground
什么是is not assignable
赋值就是让一个特定类型的变量对应一个特定的实例。如果您与实例的类型不匹配,则会收到错误消息。例如:
// type string is not assignable to type number
const a: number = 'hello world' //error
// type number is assinable to type number
const b: number = 2 // ok
什么是different subtype
两种类型是相等的:如果它们不添加或删除彼此相关的细节。
两种类型不同:如果它们不相等。
A 类型是S 类型的子类型:如果A 添加细节而不从S 中删除已经存在的细节。
A 类型和B 类型是S 类型的不同子类型:如果A 和B 是S 的子类型,但A 和@987654352 @ 是不同的类型。换句话说: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
注意:Structural Type
当您在 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' }。更多信息请见:Official Doc。
什么是constraint of type'X'
类型约束就是您放在“extends”关键字右侧的内容。在下面的示例中,Type Constraint 是“B”。
const func = <A extends B>(a: A) => `hello!`
读取:类型约束“B”是constraint of type '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' }
案例 1:无限制
const func = <A>(a: A) => `hello!`
// call examples
const c0 = func(undefined) // ok
const c1 = func(null) // ok
const c2 = func(() => undefined) // ok
const c3 = func(10) // ok
const c4 = func(`hi`) // ok
const c5 = func({}) //ok
const c6 = func(foo) // ok
const c7 = func(foo_SubType) //ok
const c8 = func(foo_DiffSubType) //ok
案例 2:一些限制
请注意,以下限制不会影响子类型。
非常重要:在 Typescript 中,Type Constraint 不限制不同的子类型
const func = <A extends Foo>(a: A) => `hello!`
// call examples
const c0 = func(undefined) // error
const c1 = func(null) // error
const c2 = func(() => undefined) // error
const c3 = func(10) // error
const c4 = func(`hi`) // error
const c5 = func({}) // error
const c6 = func(foo) // ok
const c7 = func(foo_SubType) // ok <-- Allowed
const c8 = func(foo_DiffSubType) // ok <-- Allowed
案例 3:更多限制
const func = <A extends SubType>(a: A) => `hello!`
// call examples
const c0 = func(undefined) // error
const c1 = func(null) // error
const c2 = func(() => undefined) // error
const c3 = func(10) // error
const c4 = func(`hi`) // error
const c5 = func({}) // error
const c6 = func(foo) // error <-- Restricted now
const c7 = func(foo_SubType) // ok <-- Still allowed
const c8 = func(foo_DiffSubType) // error <-- NO MORE ALLOWED !
见TS playground
结论
下面的函数:
const func = <A extends Foo>(a: A = foo_SubType) => `hello!` //error!
产生此错误消息:
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) // ok! type 'Foo' will be infered and assigned to 'A'
const c1 = func(foo_SubType) // ok! type 'SubType' will be infered
const c2 = func(foo_DiffSubType) // ok! type 'DiffSubType' will be infered
因此,将具体类型分配给泛型 Type Parameter 是不正确的,因为在 TS 中,Type Parameter 可以总是被实例化为任意不同的子类型。
解决方案:
永远不要将具体类型分配给泛型类型参数,将其视为read-only!而是这样做:
const func = <A extends Foo>(a: A) => `hello!` //ok!
见TS Playground