LoginSignup
14
1

More than 1 year has passed since last update.

【TypeScript】動的なプロパティをもつオブジェクトの型を定義する

Last updated at Posted at 2022-11-05

はじめに

TypeScriptで、一部のプロパティだけが動的に変わるようなオブジェクトの型定義について、Mapped Types, インデックスシグネチャ, インターセクション型を用いて定義してみました。

最終的なコードは下記になります。

type FixedProps = {
  [key in 'prop1' | 'prop2' | 'prop3']: { value: string }
}

type DynamicProps = {
  [key: string]: {
    value1?: string,
    value2?: number,
  }
}

type Props = FixedProps & DynamicProps

Mapped Typesとは

Mapped Typesは、ある型をベースに新しい型を作る機能です。

RequiredOmitなどのutility typeの内部でも使われています。

例えば、Requiredの定義は下記のようになっています。

type Required<T> = {
    [P in keyof T]-?: T[P];
};

引数で渡される型Tのプロパティが、keyofinによって一つずつ型変数Pに入り、-?でオプショナルを外し、T[P]によって該当のプロパティの値の型を再定義しています。

定義を見て少し思ったのが、引数Tに対してextends{ [key: string]: any }とかobjectを指定して、引数をオブジェクトのみに限定するのもアリなのかなと思いました。

type Required<T extends { [key: string]: any }> = {
    [P in keyof T]-?: T[P];
};

// Type 'string' does not satisfy the constraint '{ [key: string]: any; }'
type sample = Required<string>

少し話しが逸れましたが、今回は固定のプロパティの部分で、プロパティの値の型が同じであり、繰り返しの記述を避けるため使いました。

下記の部分になります。

type FixedProps = {
  [key in 'prop1' | 'prop2' | 'prop3']: { value: string }
}

inによってユニオン型の値が1つずつ取り出されkeyとして定義されます。

最終的には下記のような型になります。

type FixedProps = {
    prop1: { value: string },
    prop2: { value: string },
    prop3: { value: string }
}

インデックスシグネチャとは

インデックスシグネチャは、プロパティ名を指定せず、プロパティ名の型と値の型のみを指定する機能です。

例えば、keystring型で値がすべてnumber型の定義は下記のようになります。

type IndexSignature = {
    [key: string]: number
}

keyは型変数なので任意の名前をつけられますが、keyKで定義されることが多いです。

また、keyの型には、string, number, symbol, テンプレートリテラル型の4つを指定できます。

例えば、テンプレートリテラル型を使ってsample_ではじまるプロパティだけに制限する場合は下記のようになります。

type TemplateLiteralPropType = { [key: `sample_${string}`]: string };

const sampleObj: TemplateLiteralPropType = {
  sample_key: 'a',
  sample_key2: 'b',
  key3: 'c',  //  Object literal may only specify known properties, and 'key3' does not exist in type 'TemplateLiteralPropType'.
}

最初に定義した一部動的なプロパティをもつオブジェクトの型においては、動的に変わるプロパティの部分で使用しています。

type DynamicProps = {
  [key: string]: {
    value1?: string,
    value2?: number,
  }
}

この場合、プロパティのkeyの型がstringで、値がvalue1value2という任意のプロパティを持つオブジェクトになります。

こうすることで、動的なプロパティの値を使いたい時などに型推論が効くようになります。

下記の変数sampleの型が上記のDynamicPropsの場合
スクリーンショット 2022-11-05 19.07.42.png

インターセクション型とは

インターセクション型は、型同士をまとめて新しい型を作る機能です。

オブジェクト型同士の場合

オブジェクト型に対してインターセクション型を使う場合は、それぞれのオブジェクトのプロパティをもつ新たな型が定義されます。

type Obj1 = {
  a: string,
  b: number,
}

type Obj2 = {
  c: boolean,
}

type IntersectionType = Obj1 & Obj2

// 下記のようになる
// type IntersectionType = {
//   a: string,
//   b: number,
//   c: boolean,
// }

プリミティブ型同士の場合

異なるプリミティブ型に対してインターセクション型を使うと、never型になります。

type Primitive = string & number

// type Primitive = never
プリミティブ型で構成されたユニオン型同士の場合

プリミティブ型で構成されたユニオン型同士に対してインターセクション型を使うと、共通するプリミティブ型のみの型になります。

type PrimitiveUnionType1 = string | number
type PrimitiveUnionType2 = string | boolean

type IntersectionType = PrimitiveUnionType1 & PrimitiveUnionType2

// type IntersectionType = string

これは一つ前で説明したプリミティブ型同士のインターセクション型はnever型になるという性質のためです。

上記の場合、PrimitiveUnionType1PrimitiveUnionType2の組み合わせは下記の4通りです。

string & string   // => string
string & boolean  // => never
number & string   // => never
number & boolean  // => never

上記のように、プリミティブ型で構成されたユニオン型同士に対してインターセクション型を使う場合、共通するプリミティブ型以外の場合はnever型になるため、共通するプリミティブ型のみの型になります。

一部動的に変わるプロパティを持つオブジェクトの型定義

以上のMapped Types, インデックスシグネチャ, インターセクション型を用いて一部のプロパティだけが動的に変わるようなオブジェクトの型定義をしてみました。

type FixedProps = {
  [key in 'prop1' | 'prop2' | 'prop3']: { value: string }
}

type DynamicProps = {
  [key: string]: {
    value1?: string,
    value2?: number,
  }
}

type Props = FixedProps & DynamicProps

もっと良い方法や間違いなどあればご指摘いただけると幸いです!

14
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
1