A在递归类型中存在类型检查错误。
我正在尝试为react-jss样式对象编写类型。
type StylesFn<P extends object> = (
props: P
) => CSS.Properties<JssValue<P>> | number | string;
type JssValue<P extends object> =
| string
| number
| Array<string | number>
| StylesFn<P>;
// @ts-ignore
interface StylesObject<K extends string = any, P extends object = {}>
extends Styles {
[x: string]: CSS.Properties<JssValue<P>> | Styles<K, P>;
}
export type Styles<K extends string = any, P extends object = {}> = {
[x in K]: CSS.Properties<JssValue<P>> | StylesObject<any, P> | StylesFn<P>
};
它工作得很好,但是typescript写了一个错误。我使用@ts-忽略
,但这并不花哨
ERROR 24:11 typecheck Interface 'StylesObject<K, P>' incorrectly extends interface 'Styles<any, {}>'.
Index signatures are incompatible.
Type 'Properties<JssValue<P>> | Styles<K, P>' is not assignable to type 'StylesFn<{}> | Properties<JssValue<{}>> | StylesObject<any, {}>'.
Type 'Properties<JssValue<P>>' is not assignable to type 'StylesFn<{}> | Properties<JssValue<{}>> | StylesObject<any, {}>'.
Type 'Properties<JssValue<P>>' is not assignable to type 'Properties<JssValue<{}>>'.
Type 'JssValue<P>' is not assignable to type 'JssValue<{}>'.
Type 'StylesFn<P>' is not assignable to type 'JssValue<{}>'.
Type 'StylesFn<P>' is not assignable to type 'StylesFn<{}>'.
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'.
这个错误是什么意思?
补充@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游乐场
原因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游乐场
下面我将分解下面错误消息的每个元素:
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'
它是一种类型,您可以分配除空或未定义之外的任何内容。例如:
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游乐场
赋值是使特定类型的变量对应于特定实例。如果您与实例的类型不匹配,则会收到错误。例如:
// 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
两种类型是相等的:如果它们不添加或删除彼此相关的细节。
两种类型是不同的:如果它们不相等。
类型A
是类型S
的子类型:如果A
添加细节而不从S
中删除已经存在的细节。
typeA
和typeB
是typeS
的不同子类型:如果A
和B
是S
的子类型,但A
和B
是不同的类型。换句话说:A
和B
为typeS
添加了细节,但它们没有添加相同的细节。
示例:在下面的代码中,以下所有语句均为真:
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'}
中,您应该阅读:Type aliasA
指向类型结构{foo:'Bar'}
。
一般语法为:type[type_alias_name]=[type_structure]
。
TypeScript类型系统只检查[type_structure]
,而不是[type_alias_name]
。这意味着在TS中,以下类型检查没有区别:type A={foo:'bar}
和type B={foo:'bar'}
。更多信息请参阅:官方Doc。
类型约束就是您放在“扩展”关键字右侧的内容。在下面的示例中,Type Constraint
是“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' }
案例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操场
下面的功能:
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
是不正确的,因为在TSType Parameter
总是可以实例化为任意不同的子类型。
解决方案:
永远不要将具体类型分配给泛型类型参数,将其视为只读
!相反,这样做:
const func = <A extends Foo>(a: A) => `hello!` //ok!
在TS游乐场看到
该错误警告您,您的Generic TypeP
不能分配给{}
,因为Generic TypeP
可以更明确地定义或限制为可能与默认值冲突的特定类型。
这意味着值{}
不能满足Generic TypeP
可以使用的所有可能类型。
让我们创建另一个只有布尔值的示例,它应该更容易理解:
interface OnlyBoolIdentityInterface<T extends boolean> {
(arg: T): T;
}
function onlyBoolGeneric<T extends boolean>(arg: T = false): T {
return arg;
}
如果您定义的Type比布尔值更具体,例如:
type TrueType = true;
如果您将函数OnlyBoolIdtyInterface
专门化为仅支持如下真值:
const onlyTrueIdentity: OnlyBoolIdentityInterface<TrueType> = onlyBoolGeneric;
即使TrueType尊重T扩展boolean
设置的约束,默认值arg: T=false
也不是TrueType
。
这就是错误试图传达给你的情况。
那么如何修复这种类型的错误呢?
有关此错误消息的详细信息,请参阅提示此错误消息的问题https://github.com/Microsoft/TypeScript/issues/29049.
稍微简短一点的解释。
抛出错误的示例:
type ObjectWithPropType<T> = {prop: T};
// Mind return type - T
const createCustomObject = <T extends ObjectWithPropType<any>>(prop: any): T => ({ prop });
type CustomObj = ObjectWithProp<string> & { id: string };
const customObj = createCustomObj<CustomObj>('value'); // Invalid
// function will only ever return {prop: T} type.
这里的问题是返回对象只会匹配属性prop
而不是任何其他属性。扩展ObjectWellPropType
给人一种错误的类型约束感。这个例子完全是一个错误的方法,它只是用来说明对象属性中的实际冲突。
如何在create函数中约束子类型:
type StringPropObject = ObjectWithPropType<string>
const createCustomObject = <T>(prop: T extends ObjectWithPropType<infer U> ? U : T): ObjectWithPropType<T> => ({ prop });
const stringObj = createCustomObject<StringPropObject>('test');
在这种情况下,函数要求参数是字符串。对象只有prop
属性,函数确实返回所需的形状。