notebook

都内でWEB系エンジニアやってます。

Conditional Typeのメモ書き

今更ながら頑張るTypeScript(strict: true)をやろうという事になったのでメモ書きをつらつらと書いていきます。

今までやりたい放題anyをぶちこんでた無法地帯に秩序をもたらすべくstrictに対応させていくのはなかなか大変です。

既存のコードに型を付けていく作業中ですがなかなかサクッとやりたいことに対応するコードが出てこない+なんども同じようだけど少し違うみたいな状況で

そのたびにあれ?これってこうだっけ?と調べてということをやっている気がしてきたので最小なパターンで実装例をアウトプットしてあとで思い出しやすくします。

ということで完全にメモ書きです。

今回はConditionalTypeについて

Conditional Type

TypeScript2.8から導入された機能

名前の通り型の定義に条件分岐を含めることができるというものです

特定の連想配列から特定の型の値を持つキーのリストを取得する

連想配列に対して[]でstringの文字列を指定したときに出てくるno index signature系の話ですね

添字に渡すキーを限定しないとエラーになるわけですが、こんなのめちゃくちゃよくやってるよ!

「連想配列に含まれるキーすべて」であればkeyofのみで事足りるのですが

今回はその中でも特定の型の値を持つキーのみを抽出したい場合のパターンです

サンプルでコードを書いてみました。

interface Hoge {
  hoge1: number;
  hoge2: string;
}
interface Fuga {
  id: number;
  name: string;
}
interface SampleRow {
  a: Hoge;
  b: Fuga;
  c: string;
  d: boolean;
  e: string;
}
// Kは型引数Tのキー T[K] = value の値がFugaかどうかを見てマッチする場合はK. しない場合はneverのキーのUnionType
type FugaPropertyNames<T> = { [K in keyof T]: T[K] extends Fuga ? K : never } [keyof T];

const row: SampleRow = {
  a: {
    hoge1: 1,
    hoge2: 'hoge'
  },
  b: {
    id: 1,
    name: 'fuga'
  },
  c: 'piyo',
  d: true,
  e: 'e'
};
const field: string = 'b';

const fugaKeys: FugaPropertyNames<SampleRow>[] = ['b'];
const isFugaKeys = (key: string): key is FugaPropertyNames<SampleRow> => {
  return fugaKeys.some(k => k === key);
};

// field: string
if (isFugaKeys(field)) {
  // field: "b"
  const value = row[field];
}

コメントに書いてありますがSampleRowの中でFugaという型のvalueを持つキーのリストを定義させることで

タイプガードの中ではfieldの値が"b"のみとなります

type FugaPropertyNames<T>でやりたいことはできてしまっているのですが確認の為にいくつかコードを書き換えてみます

  • fugaKeysに要素を足す
  • タイプガードの前でrow[field]を書く
  • aの方をFugaに変更してみる -> '"a"|"b"' になる

f:id:swfz:20190609150736g:plain

f:id:swfz:20190609150755g:plain

f:id:swfz:20190609150851g:plain

意図通りの動作になりました

今回の場合だとFuga型のデータのときのみなにかやりたい場合に使えるかと思います

最初Object.entriesObject.keys,Object.valuesなどを使ってできないかも考えたのですがそれぞれループの中での型がanyになってしまうので結局その中でタイプガードを書くことになりそうだったため別のアプローチを試みてみました。