TS2322 を修正するには: 「制約 'object' の別のサブタイプでインスタンス化できます」? 質問する

TS2322 を修正するには: 「制約 '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>
};

正常に動作しますが、Typescriptはエラーを書き込みます。 を使用しています@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'.

このエラーはどういう意味ですか?

ベストアンサー1

@fetzz の素晴らしい回答に賛同します。


短い答え

TLDR:この種のエラー メッセージには、2 つの一般的な原因があります。最初の原因が考えられます (下記参照)。テキストとともに、このエラー メッセージが伝えようとしている内容を詳しく説明します。

原因 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'

タイプとは{}

これは、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 プレイグラウンドで見る


とは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


Aとはdifferent subtype

2 つのタイプは、互いに関連して詳細を追加または削除しない場合は同等です。

2 つのタイプは等しくない場合、異なります。

Aは 型のサブタイプですS: は、から既存の詳細を削除せずにA詳細を追加します。S

Aと 型 はB型 の異なるサブタイプですSAと がBのサブタイプである場合S、 とAB異なる型です。 言い換えると、ABは型 に詳細を追加しますがS同じ詳細 を追加するわけではありません

例:以下のコードでは、次のすべてのステートメントが当てはまります。

  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

注記:構造タイプ

TS でキーワードの使用を確認する場合type、たとえばtype A = { foo: 'Bar' }次のように記述する必要があります:型エイリアスはA型構造を指しています{ foo: 'Bar' }

一般的な構文は次のとおりですtype [type_alias_name] = [type_structure]

Typescript の型システムは に対してのみチェックし[type_structure]、 に対してはチェックしません。つまり、TS では、と[type_alias_name]の間に型チェックの点で違いはありません。詳細については、公式ドキュメントを参照してください。type A = { foo: 'bar }type B = { foo: 'bar' }


constraint of type「X」とは何か

制約は、単に「extends」キーワードの右側に配置するものです。以下の例では、Type Constraint「B」です。

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

読み取り:型制約「B」はconstraint of type 'A'


エラーが発生する理由

説明のために 3 つのケースを紹介します。各ケースで異なるのは のみで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

したがって、TS では常に任意の異なるサブタイプにインスタンス化できるため、ジェネリックに具体的な型を割り当てることType Parameterは誤りです。Type Parameter

解決:

ジェネリック型パラメータに具体的な型を割り当てないでください。 として考えてくださいread-only。 代わりに、次のようにします。

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

TSプレイグラウンドで見る

おすすめ記事