typescript 可以使用约束“对象”的不同子类型实例化

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/56505560/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 08:03:34  来源:igfitidea点击:

could be instantiated with a different subtype of constraint 'object'

typescript

提问by teux

A have a typecheck error in recursive types.

A 在递归类型中存在类型检查错误。

I am trying to write types for react-jss styles object.

我正在尝试为 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>
};

It works fine, but typescript writes error. I use @ts-ignore, but this is not fancy

它工作正常,但打字稿写错误。我使用@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'.

What this error means?

这个错误是什么意思?

回答by Flavio Vilante

Complementing @fetzz great answer.

补充@fetzz 很好的答案。



SHORT ANSWER

简答

TLDR;There are two common causes for this kind of error message. You are doing the first one (see bellow). Along the text I explain in rich details what this error message want to convey.

TLDR;此类错误消息有两个常见原因。你正在做第一个(见下文)。在文本中,我详细解释了此错误消息想要传达的内容。

CAUSE 1:In typscript, a generic type parameter is read-only. So it's not allowed to assign a concrete value to it. Following you can see an example of the 'problem' and the 'problem solved', so you can compare the difference and see what changes:

原因 1:在 typscript 中,泛型类型参数是只读的。所以不允许给它分配一个具体的值。下面你可以看到'问题'和'问题解决'的例子,所以你可以比较差异,看看有什么变化:

PROBLEM

问题

const func1 = <A extends string>(a: A = 'foo') => `hello!` // Error!

const func2 = <A extends string>(a: A) => {
    //stuff
    a = `foo`  // Error!
    //stuff
}

SOLUTION

解决方案

const func1 = <A extends string>(a: A) => `hello!` // ok

const func2 = <A extends string>(a: A) => { //ok
    //stuff
    //stuff
}

See in: TS Playground

见:TS游乐场

CAUSE 2:Although you are not doing bellow error in your code. It is also a normal circunstance where this kind of error message pop up. You should avoid to do this:

原因 2:尽管您的代码中没有出现以下错误。出现这种错误信息也是正常情况。你应该避免这样做:

Repeat (by mistaken) the Type Parameterin a class, type or interface.

Type Parameter在类、类型或接口中重复(错误地)。

Don't let the complexity of bellow code confuse you, the only thing I want you to concentrate is how the removing of the leter 'A' solves the problem:

不要让波纹管代码的复杂性使您感到困惑,我唯一希望您关注的是删除字母“A”如何解决问题:

PROBLEM:

问题:

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!
})

SOLUTION:

解决方案:

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
})

See in: TS Playground

见:TS游乐场



LONG ANSWER

长答案



UNDERSTANDING THE ERROR MESSAGE

理解错误信息

Following I'll decompose each element of the error message below:

下面我将分解以下错误消息的每个元素:

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'


WHAT IS TYPE {}

什么是类型 {}

It's a type that you can assign anything except null or undefined. For example:

这是一种您可以分配除 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...

See in: TS Playground

见:TS游乐场



WHAT IS is not assignable

什么是 is not assignable

To assignis to make a variable of a particular type correspond to a particular instance. If you mismatch the type of the instance you get an error. For example:

赋值就是让一个特定类型的变量对应一个特定的实例。如果实例类型不匹配,则会出现错误。例如:

// 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



WHAT IS A different subtype

什么是 different subtype

Two types are equals: if they do not add or remove details in relation to each other.

两种类型是相等的:如果它们不添加或删除彼此相关的细节。

Two types are different: if they are not equals.

两种类型是不同的:如果它们不相等。

Type Ais a subtype of type S: if Aadds detail withoutremoving already existent detail from S.

TypeA是 type 的子类型S:如果在从 中删除已经存在的细节的情况下A添加细节。S

type Aand type Bare different subtypes of type S: If Aand Bare subtypes of S, but Aand Bare different types. Said in other words: Aand Badds detail to the type S, but they do not add the same detail.

typeA和 typeB是type的不同子类型S:如果AB是 的子类型S,但是AB是不同的类型。换句话说:AB为 type 添加了 detail S但它们没有添加相同的 detail

Example:In code below, all following statements are true:

示例:在下面的代码中,以下所有语句都是正确的:

  1. A and D are equal types
  2. B is subtype of A
  3. E is not subtype of A
  4. B and C are different subtype of A
  1. A 和 D 是相等的类型
  2. B 是 A 的亚型
  3. E 不是 A 的亚型
  4. 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

NOTE: Structural Type

When you see in TS the use of typekeyword, for instance in type A = { foo: 'Bar' }you should read: Type alias Ais pointing to type structure { foo: 'Bar' }.

The general syntax is: type [type_alias_name] = [type_structure].

Typescript type system just checks against [type_structure]and not against the [type_alias_name]. That means that in TS there's no difference in terms of type checking between following: type A = { foo: 'bar }and type B = { foo: 'bar' }. For more see: Official Doc.

注意结构类型

当您在 TS 中看到type关键字的使用时,例如在type A = { foo: 'Bar' }您应该阅读:Type alias Ais指向 type structure{ foo: 'Bar' }

一般语法是:type [type_alias_name] = [type_structure].

Typescript 类型系统只检查[type_structure]而不是检查[type_alias_name]. 这意味着在 TS 中,以下内容在类型检查方面没有区别:type A = { foo: 'bar }type B = { foo: 'bar' }。更多信息请参见:官方文档



WHAT IS constraint of type'X'

什么是constraint of type“X”

The Type Constraintis simple what you put on right-side of the 'extends' keyword. In below example the Type Constraintis 'B'.

类型约束是简单的,你穿什么关键字的“扩展”右侧。在下面的例子中Type Constraint是'B'。

const func = <A extends B>(a: A) => `hello!`

Reads: Type Constraint 'B' is the constraint of type 'A'

阅读:类型约束“B”是constraint of type 'A'



WHY THE ERROR HAPPENS

为什么会发生错误

To ilustrate I'll show you three cases. The only thing that will vary in each case is the Type Constraint, nothing else will change.

为了说明,我将向您展示三个案例。在每种情况下唯一会有所不同的是Type Constraint,其他都不会改变。

What I want you to notice is that the restriction that Type Constraintimposes to Type Parameterdoes not include different subtypes. Let's see it:

我想让您注意的是,Type Constraint强加给的限制Type Parameter不包括不同的子类型。让我们来看看它:

Given:

鉴于:

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: NO RESTRICTION

案例 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

CASE 2: SOME RESTRICTION

案例 2:一些限制

Note below that restriction does not affect subtypes.

请注意下面的限制不会影响子类型。

VERY IMPORTANT: In Typescript the Type Constraintdoes not restrict different subtypes

非常重要:在 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

CASE 3: MORE CONSTRAINED

案例 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 !

See in TS playground

TS 游乐场看到



CONCLUSION

结论

The function below:

功能如下:

const func = <A extends Foo>(a: A = foo_SubType) => `hello!` //error!

Yields this error message:

产生此错误消息:

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)

Because Typescript infers Afrom the function call, but there's no restriction in the language limiting you to call the function with different subtypes of 'Foo'. For instance, all function's call below are considered valid:

因为 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

Therefore assigning a concrete type to a generic Type Parameteris incorrect because in TS the Type Parametercan alwaysbe instantiated to some arbitrary different subtype:

因此将具体类型分配给泛型Type Parameter是不正确的,因为在 TSType Parameter总是可以实例化为一些任意不同的子类型:

Solution:

解决方案:

Never assign a concrete type to a generic type parameter, consider it as read-only! Instead, do this:

永远不要将具体类型分配给泛型类型参数,将其视为read-only! 相反,请执行以下操作:

const func = <A extends Foo>(a: A) => `hello!` //ok!

See in TS Playground

TS Playground 中查看

回答by Fetz

That error is warning, that your Generic Type Pcan't be assigned {}, since the Generic Type Pcan be a more defined (or restricted) type.

该错误是警告,您的 Generic Type Pcan't beassigned {},因为 Generic TypeP可以是更定义(或限制)的类型。

That means that the value {}will not satisfy all possible Types that can be used for the Generic Type P.

这意味着该值{}不会满足可用于泛型类型的所有可能类型P

For example I can have a generic like this (that has the same error):

例如,我可以有一个这样的泛型(具有相同的错误):

function fn<T extends boolean>(obj: T = false) {
}

and you can have a Type that is more specific than a boolean like this:

你可以有一个比布尔值更具体的类型,如下所示:

type TrueType = true;

and if you pass it to the Generic function fn:

如果你将它传递给通用函数 fn:

const boolTrue: TrueType = true;
fn(boolTrue);

the assign to false is not respecting the TrueTypeeven if TrueTyperespects the constraint of the generic T extends boolean

分配给 false 不尊重TrueType即使TrueType尊重泛型的约束T extends boolean

For more context about this error message see the issue that suggested this error message https://github.com/Microsoft/TypeScript/issues/29049.

有关此错误消息的更多上下文,请参阅建议此错误消息https://github.com/Microsoft/TypeScript/issues/29049 的问题

回答by ford04

Minimal example

最小的例子

The issue gets clearer with a shortened version producing the same error:

使用产生相同错误的缩短版本,问题变得更加清晰:

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
};

Error (take a look at Playgroundfor full message stack):

错误(查看Playground以获取完整的消息堆栈):

'{}' is assignable to the constraint of type 'P' (a), but 'P' could be instantiated with a different subtype of constraint 'object' (b).

' {}' 可分配给类型 ' P' (a)的约束,但P可以使用约束 ' object' (b)的不同子类型实例化 ' ' 。



What is the problem?

问题是什么?

  1. StylesObjecthas to be a subtype (compatible to) Styles.
  2. By writing extends Styles, we don't set a generic type argument for Styles. So Pwill be instantiated with the default{}type.
  3. StylesObject<P>effectivelywants to extend from Styles<{}>, but the two are incompatible.
  1. StylesObject必须是一个子类型(兼容)Styles
  2. 通过编写extends Styles,我们不会为 设置泛型类型参数Styles。因此 P将使用默认{}类型进行实例化。
  3. StylesObject<P>有效地想从Styles<{}>,但两者不相容
const myStylesObject: StylesObject<{ foo: string }> = ...;
const styles: Styles<{}> = myStylesObject // error: incompatible

In principle, StylesObjectallows any argument type, that extends constraint object(default = {}not important here). And Styles<{}>would be compatible to object. This is what error part (a)says.

原则上,StylesObject允许任何扩展约束的参数类型object= {}此处默认不重要)。并且Styles<{}>将兼容object. 这就是错误部分(a)所说的。

But what, if Pis a more narrow subtype of object, like myStylesObjectin above code? It wouldn't work anymore. This is what error part (b)says.

但是,如果P是更窄的子类型object,就像myStylesObject上面的代码一样?它不会再工作了。这就是错误部分(b)所说的。

Dogs and animals analogy

狗和动物的比喻

Playground游乐场further infos更多信息
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'.


Solutions

解决方案

Option 1: Use type alias for StylesObject

选项 1:使用类型别名 StylesObject

type StylesObject<K extends string = any, P extends object = {}> = Styles<K, P> & {
    [x: string]: CSS.Properties<JssValue<P>> | Styles<K, P>;
}

StylesObjectis the same type as before by extending from Styleswith &/ intersection. Advantage: You now can declare Styles<K, P>, which would be not possible with interface. More infos in this answer.

StylesObject通过从Styles&/ 交点扩展,与之前的类型相同。优点:您现在可以声明Styles<K, P>,而这在 interface 中是不可能的此答案中的更多信息。

I recommend this variant, as no other changes are required. Take a look at the Playground.

我推荐这个变体,因为不需要其他更改。看看游乐场

Option 2: Use method declaration in StylesFn

选项 2:在中使用方法声明 StylesFn

type StylesFn<P extends object> = {
    create(props: P): CSS.Properties<JssValue<P>> | number | string
}

This requires StylesFnto be an object type with a method declaration, like create. Playgroundand further infos

这需要StylesFn是带有方法声明的对象类型,例如create. 游乐场更多信息

回答by Mr Br

A bit shorter explanation.

稍微简短的解释。

Example that throws error:

引发错误的示例:

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.

The problem here is that the return object will only ever match the attribute propand not any other attribute. Extending the ObjectWithPropTypegives a false sense of type constraint. This example is all in all a wrong approach it was used just for illustration to show actual conflict in object attributes.

这里的问题是返回对象只会匹配属性prop而不匹配任何其他属性。扩展ObjectWithPropType给出了错误的类型约束。这个例子完全是一种错误的方法,它仅用于说明以显示对象属性中的实际冲突。

How to constrain subtype in create function:

如何在 create 函数中约束子类型:

type StringPropObject = ObjectWithPropType<string>

const createCustomObject = <T>(prop: T extends ObjectWithPropType<infer U> ? U : T): ObjectWithPropType<T> => ({ prop });

const stringObj = createCustomObject<StringPropObject>('test');

In this case, the function requires the argument to be a string. The object only has propattribute and function do return required shape.

在这种情况下,该函数要求参数为字符串。该对象只有prop属性和函数确实返回所需的形状。