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>
};
它可以正常工作,但打字稿会写错误。我使用@ ts-ignore,但这并不花哨
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'.
此错误是什么意思?
答案 0 :(得分:5)
补充上面的好答案。
TLDR; 造成这种错误消息的主要原因有两个:
原因1::当您为通用Type Parameter
分配“具体类型”时。这在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
}
原因2:如果您错误地在类中重复了Type Parameter
,请键入或界面。
问题:
interface Foo<A> {
//bellow 'A' is duplicated in relation to above 'A'
map: <A,B>(f: (_: A) => B) => Foo<B>
}
const makeFoo = <A>(a: A): Foo<A> => ({
map: f => makeFoo(f(a)) //error!
})
解决方案:
interface Foo<A> {
// duplication removed
map: <B>(f: (_: A) => B) => Foo<B>
}
const makeFoo = <A>(a: A): Foo<A> => ({
map: f => makeFoo(f(a)) //ok
})
我要遵循的方法是分解错误消息的每个元素:
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'
注意::在将来的TS will change上,错误消息从'{}'到
unknown
。他们发现使用'{}'只会使人们感到困惑。
我将在所有其他示例中使用波纹管功能结构:
const func = <A extends B>(a: A) => `hello!`
让我们使用这个名称约定:
Type Parameter
Type Constraint
(编译器称其为:“ A”类型的约束)different subtype
这是定义:
两种类型相等:如果它们具有完全相同的属性和方法,则不多不少。
两种类型不同:如果它们不相等。
类型A
是类型B
的子类型:如果类型A
与类型B
等于 而且还会添加更多信息(包括更多属性和/或方法)。
请注意,概念重叠。两种类型可以是特定类型的子类型,并且同时被认为彼此不同(不同子类型)。
这里是一个例子:
// An arbitrary type
type Foo = {
readonly prop1: `bar`
}
// A subtype of 'Foo'
type SubType = {
readonly prop1: `bar`
readonly prop2: `thux` //new property introduced
}
// A different subtype of 'Foo' (but still a subtype)
type DiffSubType = {
readonly prop1: `bar`
readonly prop3: `different` //new property introduced, but different that the property 'prop2' introduced up-above
}
请注意,我们可以创建特定类型几乎无限的 个子类型。如您所见,这是编译器消息的核心。
{}
类型{}
表示“具有属性和/或方法”的任何类型。换一种说法,
类型{}
对应于您可以想象的除null
和undefined
之外的任何类型。
请记住,在Javascript中,某些原语,例如'string','numbers'和函数(null
和undefined
除外)也具有相关的属性和方法。 (例如:'hello world'.length()
或myFunction.bind(...)
。
让我们检查一下。假设存在以下实例:
const foo: Foo = {...} // definition omited for brevity
const foo_SubType: SubType = {...}
const foo_DiffSubType: DiffSubType = {...}
因此,我们可以通过执行以下操作来验证代表“ {}”类型的内容:
// function definition
const func = <A extends {}>(a: A) => `hello!`
// many ways to call it
const c0 = func(undefined) // error: Argument of type 'undefined' is not assignable to parameter of type '{}'
const c1 = func(null) // error: Argument of type 'null' is not assignable to parameter of type '{}'
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
TYPE CONSTRAINT
工作原理 为说明起见,我将向您展示三种情况。在每种情况下唯一会改变的是Type Constraint
,其他都不会改变。
请注意,Type Constraint
对Type Parameter
施加的限制仅包含 不同类型,不包括不同子类型。我们来看一下:
无限制
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
某些限制
请注意,限制不影响子类型。
在打字稿中,Type Constraint
仅限制了上限,它没有告诉您Type Parameter
的精确度 。
const f = <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
更多约束
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) //ok <-- NO MORE ALLOWED !
将具体类型分配给泛型Type Parameter
是不正确的,因为Type Parameter
可以总是实例化为某些任意不同的子类型:
代码:
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)
解释:
在函数'func'中,Type Parameter
'A'不仅可以接受Type Constraint
'Foo',还可以接受其所有可能的子类型(其中的无限数量)。
当您说要将Type Parameter
'A'缺省设置为'Foo'类型的 specific 子类型时(在上面的示例中为A = foo_SubType
),那么我必须提醒您,则Type Parameter
'A'可以接受Type Constraint
'Foo'的许多不同子类型,而不能仅您指定的特定子类型。
解决方案:
const func = <A extends Foo>(a: A) => `hello!`
答案 1 :(得分:3)
该错误警告,由于通用类型P
可以是更定义(或受限制)的类型,因此无法为您的通用类型{}
分配P
。>
这意味着值{}
将不能满足可用于通用类型P
的所有可能的类型。
例如,我可以有一个像这样的泛型(同样的错误):
function fn<T extends boolean>(obj: T = false) {
}
,您可以拥有一个比布尔型更具体的类型:
type TrueType = true;
,如果将其传递给泛型函数fn:
const boolTrue: True = true;
fn(boolTrue);
将false分配给false时,即使TrueType遵守泛型T的扩展布尔值
有关此错误消息的更多上下文,请参阅提示此错误消息https://github.com/Microsoft/TypeScript/issues/29049的问题。
答案 2 :(得分:1)
使用缩短版本会产生相同错误的问题会更加明显:
interface StylesObject<P extends object = {}> extends Styles {
// ^~~~~~~~~~~^ same error as in question
foo: (props: P) => void;
}
type Styles<P extends object = {}> = {
foo: (props: P) => void
};
错误(请查看See it here at regex101,了解完整的消息堆栈)
'
{}
'可分配给类型'P
'的约束, (a) ,但'P
'可以使用约束'object
'的另一个子类型实例化 (b) 。
StylesObject
必须是Styles
的子类型(兼容)。extends Styles
,我们没有为Styles
设置泛型类型参数。因此,P
将使用 default {}
类型的实例化。StylesObject<P>
有效地想要从Styles<{}>
扩展,但是两个不兼容。const myStylesObject: StylesObject<{ foo: string }> = ...;
const styles: Styles<{}> = myStylesObject // error: incompatible
原则上,StylesObject
允许扩展约束object
的任何参数类型(默认= {}
在这里不重要)。并且Styles<{}>
将与object
兼容。这是(a)所说的错误部分。
但是,如果P
是object
的更窄子类型,如上面的代码中的myStylesObject
呢?它不再工作了。这是(b)部分所说的错误。
const playWithDog = (dog: Dog) => { dog.isBarking = true }
const handleAnimal: (animal: Animal) => void = playWithDog
// error, cannot assign function that wants to deal with dogs (specific animals)
// to a variable type that describs a callback for all sorts of animals.
function feedAnimal(animalFeeder: (animal: Animal) => void) { }
feedAnimal((dog: Dog) => { dog.isBarking = true })
// Error: Type 'Animal' is not assignable to type 'Dog'.
StylesObject
使用类型别名type StylesObject<K extends string = any, P extends object = {}> = Styles<K, P> & {
[x: string]: CSS.Properties<JssValue<P>> | Styles<K, P>;
}
StylesObject
与以前的类型相同,是从Styles
扩展为&
/交点。优点:您现在可以声明further infos的Styles<K, P>
。 would be not possible with interface中的更多信息。
我建议使用此变体,因为不需要其他更改。看看this answer。
StylesFn
中使用方法声明type StylesFn<P extends object> = {
create(props: P): CSS.Properties<JssValue<P>> | number | string
}
这要求StylesFn
是带有方法声明的对象类型,例如create
。 Playground和Playground
答案 3 :(得分:0)
简短的解释。
抛出错误的示例:
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
匹配,而与其他任何属性都不匹配。扩展ObjectWithPropType
会产生错误的类型约束。该示例完全是错误的方法,仅用于说明对象属性的实际冲突。
如何在创建函数中约束子类型:
type StringPropObject = ObjectWithPropType<string>
const createCustomObject = <T>(prop: T extends ObjectWithPropType<infer U> ? U : T): ObjectWithPropType<T> => ({ prop });
const stringObj = createCustomObject<StringPropObject>('test');
在这种情况下,该函数要求参数为字符串。该对象仅具有prop
属性,该函数会返回所需的形状。