web-dev-qa-db-ja.com

TypeScriptで既知のキーと不明なキーを持つオブジェクトを入力するにはどうすればよいですか

2つの既知のキーと既知のタイプを持つ1つの不明なキーを持つ次のオブジェクトのTypeScriptタイプを作成する方法を探しています。

interface ComboObject {
  known: boolean
  field: number
  [U: string]: string
}

const comboObject: ComboObject = {
  known: true
  field: 123
  unknownName: 'value'
}

TypeScriptではすべてのプロパティが特定のインデックスシグネチャのタイプと一致する必要があるため、このコードは機能しません。ただし、インデックスシグネチャを使用するつもりはありません。型はわかっているが名前がわからない単一のフィールドを入力したいのですが。

これまでの唯一の解決策は、インデックスシグネチャを使用して、すべての可能な型のユニオン型を設定することです。

interface ComboObject {
  [U: string]: boolean | number | string
}

しかし、既知のフィールドでの不正な型の許可や、任意の数の未知のキーの許可を含む多くの欠点があります。

より良いアプローチはありますか? TypeScript 2.8条件付きタイプの何かが役立つでしょうか?

9
Jacob Gillespie

あなたはそれを求めて。

特定の型が共用体であるかどうかを検出するために、いくつかの型操作を実行してみましょう。それが機能する方法は、条件付きタイプの distributive プロパティを使用して、共用体を構成要素に展開し、各構成要素が共用体よりも狭いことに注意することです。それが本当ではない場合、それは組合が構成要素を1つしか持っていないためです(したがって組合ではありません):

type IsAUnion<T, Y = true, N = false, U = T> = U extends any
  ? ([T] extends [U] ? N : Y)
  : never;

次に、それを使用して、指定されたstringタイプが単一の文字列リテラルであるかどうかを検出します(つまり、stringではなく、neverではなく、共用体ではありません)。

type IsASingleStringLiteral<
  T extends string,
  Y = true,
  N = false
> = string extends T ? N : [T] extends [never] ? N : IsAUnion<T, N, Y>;

これで、特定の問題について取り上げることができます。 BaseObjectComboObjectの一部として定義します。これは、直接定義できます。

type BaseObject = { known: boolean, field: number };

エラーメッセージを準備するために、ProperComboObjectを定義してみましょう。そうすれば、失敗したときに、エラーが何をすべきかについてのヒントが得られます。

interface ProperComboObject extends BaseObject {
  '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!': string
}

ここからメインコースです。 VerifyComboObject<C>は、C型を受け取り、目的のComboObject型に準拠している場合はそのままの形で返します。それ以外の場合は、エラーに対してProperComboObject(これにも準拠しません)を返します。

type VerifyComboObject<
  C,
  X extends string = Extract<Exclude<keyof C, keyof BaseObject>, string>
> = C extends BaseObject & Record<X, string>
  ? IsASingleStringLiteral<X, C, ProperComboObject>
  : ProperComboObject;

CBaseObjectと残りのキーXに分解することで機能します。 CBaseObject & Record<X, string>と一致しない場合は、失敗しています。これは、BaseObjectではないか、余分な非stringプロパティが含まれているためです。次に、XIsASingleStringLiteral<X>でチェックして、残りのキーが正確に1つあることを確認します。

ここで、入力パラメーターがVerifyComboObject<C>に一致することを必要とするヘルパー関数を作成し、入力を変更せずに返します。正しいタイプのオブジェクトが必要なだけの場合、ミスを早期に発見できます。または、シグネチャを使用して、独自の関数に適切なタイプを要求させることができます。

const asComboObject = <C>(x: C & VerifyComboObject<C>): C => x;

試してみましょう:

const okayComboObject = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value'
}); // okay

const wrongExtraKey = asComboObject({
  known: true,
  field: 123,
  unknownName: 3
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const missingExtraKey = asComboObject({
  known: true,
  field: 123
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const tooManyExtraKeys = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value',
  anAdditionalName: 'value'
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

最初のものは、必要に応じてコンパイルされます。最後の3つは、追加のプロパティの数とタイプに関係するさまざまな理由で失敗します。エラーメッセージは少し不可解ですが、私ができる最善の方法です。

実際のコードは The Playground で確認できます。


繰り返しますが、量産コードには推奨しません。私は型システムをいじるのが大好きですが、これは特に 複雑で壊れやすい と感じ、私は 予期しない結果 に対して責任を感じたくありません。

お役に立てば幸いです。幸運を!

8
jcalz

素敵な1つ@jcalz

それは私が望んでいた場所に到達するための良い洞察を私に与えました。私はいくつかの既知のプロパティを持つBaseObjectが好きで、BaseObjectは必要なだけBaseObjectを持つことができます。

type BaseObject = { known: boolean, field: number };
type CoolType<C, X extends string | number | symbol = Exclude<keyof C, keyof BaseObject>> = BaseObject & Record<X, BaseObject>;
const asComboObject = <C>(x: C & CoolType<C>): C => x;

const tooManyExtraKeys = asComboObject({
     known: true,
     field: 123,
     unknownName: {
         known: false,
         field: 333
     },
     anAdditionalName: {
         known: true,
         field: 444
     },
});

この方法で、あまり変更せずに、既に持っている構造の型チェックを取得できます。

ty

1
hugo00

これは私にとっては機能します(既知のフィールドとフィールドが両方とも必須プロパティである場合):

(v3.8.3)

type ComboObject = {
  known: boolean
  field: number
} | {
  [key: string]: string
}

const comboObject: ComboObject = {
  known: '123',
  field: 123,
  unknownName: ''
}

https://www.typescriptlang.org/play?#code/C4TwDgpgBAwg9gWwEZwPJIFYQMbCgXigG8AoKKAawDs4B3KgLihTgBsIBDKsqAMwEsIrACZMqAV2QQATiQC+UAD7EeAbQoQQTAM7Bp-KgHMAujr0HD8kiWxwquqLeRpMOYE3jP0WXARXlqOkY+DlZtCAAaHgEhUSgARgAmAGYo8nEqQPoAOQ4ECCYAckL5IA

0
YungYuan Chang