可以使用约束“对象”的其他子类型实例化

teu*_*eux 17 typescript

在递归类型中有类型检查错误。

我正在尝试为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>
};
Run Code Online (Sandbox Code Playgroud)

它工作正常,但打字稿会写错误。我使用@ 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'.
Run Code Online (Sandbox Code Playgroud)

此错误是什么意思?

Fla*_*nte 235

补充@fetzz 很好的答案。


简答

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

原因 1:在打字稿中,不允许将具体实例分配给类型参数。下面你可以看到'问题'和'问题解决'的例子,所以你可以比较差异,看看有什么变化:

问题

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

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

解决方案

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

const func2 = <A extends string>(a: A) => { //ok
    //stuff
    //stuff
}
Run Code Online (Sandbox Code Playgroud)

见: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!
})
Run Code Online (Sandbox Code Playgroud)

解决方案:

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
})
Run Code Online (Sandbox Code Playgroud)

见: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'
Run Code Online (Sandbox Code Playgroud)

什么是类型 {}

这是一种您可以分配除 null 或 undefined 之外的任何内容的类型。例如:

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'
Run Code Online (Sandbox Code Playgroud)

见:TS游乐场


什么是 is not assignable

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

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...
Run Code Online (Sandbox Code Playgroud)

什么是 different subtype

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

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

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

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

示例:在下面的代码中,以下所有语句都为真:

  1. A 和 D 是相等的类型
  2. B 是 A 的亚型
  3. E 不是 A 的亚型
  4. B和C是A的不同亚型
// 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

Run Code Online (Sandbox Code Playgroud)
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'}
Run Code Online (Sandbox Code Playgroud)
type A = number
type B = 2
type C = 7
type D = number
type E = `hello world`
Run Code Online (Sandbox Code Playgroud)

注意结构类型

当您在 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' }。更多信息请参见:官方文档


什么是constraint of type“X”

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

type A = boolean
type B = true
type C = false
type D = boolean
type E = number
Run Code Online (Sandbox Code Playgroud)

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


为什么会发生错误

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

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

鉴于:

const func = <A extends B>(a: A) => `hello!`
Run Code Online (Sandbox Code Playgroud)

案例 1:无限制

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' }
Run Code Online (Sandbox Code Playgroud)

案例 2:一些限制

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

非常重要:在 Typescript 中Type Constraint ,不限制不同的子类型

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

Run Code Online (Sandbox Code Playgroud)

案例 3:更受限制

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
Run Code Online (Sandbox Code Playgroud)

TS 游乐场看到


结论

功能如下:

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 !

Run Code Online (Sandbox Code Playgroud)

产生此错误消息:

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)
Run Code Online (Sandbox Code Playgroud)

因为 Typescript 是A从函数调用中推断出来的,但是语言中没有限制您使用不同的 'Foo' 子类型调用函数。例如,下面的所有函数调用都被认为是有效的:

const func = <A extends Foo>(a: A = foo_SubType) => `hello!` //error!
Run Code Online (Sandbox Code Playgroud)

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

解决方案:

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

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)
Run Code Online (Sandbox Code Playgroud)

TS Playground 中查看

  • “&lt;A extends Foo&gt;(a: A) =&gt; `hello!`”不等于“(a: Foo) =&gt; `hello!`”吗? (5认同)

Fet*_*etz 16

该错误是警告,P无法分配您的通用类型{},因为通用类型P可以是更定义(或受限制)的类型。

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

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

function fn<T extends boolean>(obj: T = false) {
}
Run Code Online (Sandbox Code Playgroud)

并且您可以拥有一个比布尔类型更具体的类型,如下所示:

type TrueType = true;
Run Code Online (Sandbox Code Playgroud)

并将其传递给泛型函数fn:

const boolTrue: TrueType = true;
fn(boolTrue);
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我不明白这两个答案。我*认为*我了解了使其不可能的机制,但我不明白*为什么*这将是一个有意的警告。使用您答案中的“fn”,“fn(true)”和“fn(false)”都是正确的,对吧?函数定义中的“= false”部分不是给它一个默认值,所以“fn()”相当于“fn(false)”吗?为什么“obj”可能为“true”会影响我的意图,即默认参数应为“false”? (6认同)
  • @ShioT,当您创建通用函数时,您允许使用该函数的人使其新版本更加专业化。因此,如果制作一个仅接受真值的新版本,您的默认值将破坏该可能的专用版本[也许我创建的其他示例代码会有帮助](https://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgEIHt0BsCSATCcYMATwDEQAeAFQD5kBvAWACHl3kAKOKAcwC5k1AJSDqAblYBfVqxgBXEAjDB0IZ MAJFSGbDWQQAHpBB4AzsgBGmLBDgha3PmOQBeZPCxmIooY1YcyFAQYPJQ6jy8kiwyLKykAA4o1FDyENQkSW7IYKkQ0awIamZgyADyIFgkKWn4hCqkFIK6uFoN5FQ16ZkQ9O6a9cQkLQUsQA ) (3认同)