search
LoginSignup
132
Help us understand the problem. What are the problem?

posted at

updated at

Organization

6歳娘「パパ、条件によって変わる型を作りたいの」

とある休日

娘「パパ」

ワイ「なんや?娘ちゃん」

娘「あのね、お友達が」
娘「TypeScriptの便利な型を忘れちゃったの」

ワイ「そうなんや」
ワイ「便利な型を忘れてもうて、どうなってんねんそれ」
ワイ「ほな、ワイも一緒に考えてあげるから、その型の特徴を教えてみてよ」

娘「ええとね、例えば」

type User = {
  firstName: string
  familyName: string
}

娘「↑この型を」

type PartialUser = {
  firstName?: string // オプショナルになった
  familyName?: string // オプショナルになった
}

娘「↑こんな型に変身させてくれる」
娘「そんな便利な型らしいんだけど」

ワイ「おー、Partial<T>やないか」
ワイ「その特徴はもう完全にコーンフレーク・・・やなくてPartial<T>やがな」

娘「コーンフレーク?」

ワイ「いやPartial<T>のことやがな」

Partial<T>

// 全てのプロパティをオプショナルにした PartialUser 型を定義する
type PartialUser = Partial<User>

ワイ「↑こうするだけやがな」
ワイ「型を渡すと、それを元に別の型を返してくれる」
ワイ「型の関数みたいな、便利なやつやで」

娘「でも分からないの」

ワイ「何が分からへんのよ」

娘「いや私もPartial<T>だと思ったんだけどね」
娘「お友達が言うには」

type User = {
  tag: string
  firstName: string
  familyName: string
}

娘「↑こんな風に、tagっていうstring型のプロパティがあった場合には」
娘「その便利な型を使っても」

type PartialUser = {
  tag: string // ここだけオプショナルにならない
  firstName?: string
  familyName?: string
}

娘「↑こんな風に、tagだけはオプショナルにならないって言ってた」

ワイ「ほなPartial<T>と違うか」
ワイ「Partial<T>は、全てのプロパティをオプショナルにしてしまうもんな」

娘「そうなの」

自作の便利型

ワイ「ほな、こんな便利な型を自作すればええんとちゃうか?」

// tag 以外のプロパティをオプショナルにしてくれる便利型
type PartialWithoutTag<T> = Partial<T> & { tag: string }

ワイ「↑こうや」
ワイ「この型を使えば」

type User = {
  tag: string
  firstName: string
  familyName: string
}

ワイ「↑この型を」

type PartialUser = PartialWithoutTag<User>

ワイ「↑こうして」

type PartialUser = {
  tag: string // ここだけオプショナルにならない
  firstName?: string
  familyName?: string
}

ワイ「↑こんな型を作ってくれるイメージやで」

娘「でも、その型って」

  • 全てのプロパティをオプショナルにしつつ
  • tagというプロパティを追加する

娘「↑こんな感じの型だから」

type Hoge = {
  aaa: string
}

娘「↑こう、元々tagを持っていない型を渡したときに」

type Hoge = {
  tag: string // プロパティが増えている
  aaa?: string
}

娘「↑こう、tagプロパティが増えちゃうでしょ」

ワイ「せやな」

勝手にtagプロパティを増やして欲しくはない

娘「元々tagプロパティがない型を渡した場合には」
娘「tagプロパティを増やして欲しくはないの」

ワイ「ぐぬぬ」
ワイ「つまり・・・」

  • 元の型を加工して返してくれる便利な型
  • 全てのプロパティをオプショナルにしてくれる
    • tagプロパティがあった場合
      → そこだけはオプショナルにしない
    • tagプロパティがない場合
      → ないままでいい

ワイ「↑こういうことか・・・」

娘「そう」
娘「そういう、条件によって変わる型を作りたいの」

ワイ「ほなConditional Typesやないか」

Conditional Types

ワイ「ええと」

type PartialWithoutTag<T> =
  T extends { tag: string } // 条件
  ? Partial<T> & { tag: string } // 真の場合に返す型
  : Partial<T> // 偽の場合に返す型

ワイ「↑こうやな」
ワイ「三項演算子みたいに書くねん」
ワイ「いや、四項演算子か」

娘「そっか、なるほど」
娘「Conditional Typesを使えばよかったんだね」
娘「ちょっと、コメントを変えてみていい?」

ワイ「おお、ええで!」

娘「ありがとう」
娘「えっと・・・」

type PartialWithoutTag<T> =
  T extends { tag: string } // tag という string 型のプロパティを持っていた場合
  ? Partial<T> & { tag: string } // tag プロパティ以外をオプショナルにする
  : Partial<T> // そうでない場合、全てのプロパティをオプショナルにする

娘「↑こういうことだね!」

ワイ「せやな!」

娘「渡された型Tが、tagプロパティを持っていた場合には」
娘「tag以外をオプショナルにしてくれるし」
娘「そうでない場合には」
娘「全てのプロパティをオプショナルにしてくれる・・・」
娘「できたね!」
娘「パパ、ありがとう!」

ワイ「かめへん、かめへん!」

娘「でも分からないの」

ワイ「いやまだあんのかい」

tagプロパティがstring型じゃないこともある

娘「そういえばお友達が」

お友達「tagプロパティは必ずしもstring型じゃないんだ」

娘「↑こう言ってた」
娘「tagプロパティの型は、場合によって色々だって」

ワイ「ぐぬぬ」
ワイ「ほなany型やないかい」
ワイ「その特徴はもう、なんでも許してくれる便利なany型や!」

娘「any型は危険だから、tsconfig.jsonで禁止してるってお友達が言ってた」

ワイ「ほなany型と違うか」
ワイ「ええと、さっきの型は」

  • tag という string 型のプロパティがあった場合

ワイ「↑こういう条件やったけど」
ワイ「それを」

  • tag という何らかの型のプロパティがあった場合

ワイ「↑こういう条件に変えなアカン訳やな」
ワイ「どうすればええんや・・・?」

娘「あ、分かった!」

inferを使う

娘「ええと、extendsの右側の部分だけど」
娘「{ tag: string }とは限らないから」
娘「条件を広げるために」
娘「{ tag: infer U }に変えるね」

type PartialWithoutTag<T> =
  T extends { tag: infer U } // string から infer U に変更
  ? Partial<T> & { tag: string }
  : Partial<T>

娘「↑こうだね」
娘「extendsの右側で{ tag: infer U }ってすることで」
娘「tagプロパティの型を推論して」
娘「Uという型変数に入れてくれるの」

ワイ「おお」
ワイ「tagプロパティの型、つまり何か分からん型に」
ワイ「Uっていう名前をつけるイメージやな」

娘「うん」

ワイ「なるほどな」

  • tag という string 型のプロパティがあった場合

ワイ「↑こういう条件を」

  • tag という何らかの型のプロパティがあった場合

ワイ「↑こういう条件に広げたい場合に」
ワイ「inferが使えるんやな」

娘「そうだね」
娘「そして、その型変数Uを」
娘「後ろの部分で使ってあげないといけないから」

type PartialWithoutTag<T> =
  T extends { tag: infer U }
  ? Partial<T> & { tag: U } // string から U に変更
  : Partial<T>

娘「↑こうだね!」

ワイ「なるほどな」
ワイ「コメントを付け直すとすると」

type PartialWithoutTag<T> =
  T extends { tag: infer U } // tag プロパティがあった場合、その型を U とする
  ? Partial<T> & { tag: U } // tag プロパティの型は U のまま引き継ぐ
  : Partial<T>

ワイ「↑こんな感じやね」

娘「そうだね」
娘「ちなみにinferというのは、推論するって意味だね」

ワイ「なるほどな」
ワイ「tagプロパティの型を推論して、Uという型変数に入れてくれる訳やもんな」

娘「そうだね」

ワイ「そうかー」
ワイ「inferってよく分かってなかったけど」
ワイ「Conditional Typesextendsの右側で使うものだったんやな」
ワイ「そんで、条件を汎用的にできるんやな」

娘「そんな感じだね」

ワイ「さすが娘ちゃんや」
ワイ「これで、自作の便利型が1つ作れたやないか」

娘「えへへ」

まとめ

  • A extends B ? C : Dという形式で、条件分岐のロジックをもった型を作れる
    • A extends B条件にあたる
    • C真の場合に返す型
    • D偽の場合に返す型
  • Bの部分でinferというキーワードを使うことができる
    • 型を推論し型変数に格納できる
    • 何か分からない型でも条件に使うことができる
    • 型変数に格納した型は、返す型を書くときに使える

その日の夜

ワイ「Conditional Typesを忘れてしまって、思い出したいなんて」
ワイ「変わったお友達がおるんやね」
ワイ「一体どんなお友達なん?」

娘「そんなお友達はいないの」

ワイ「ファッ!?」

娘「パパに、Conditional Typesinferの使い方を理解して欲しくて」
娘「仮の問いをしてみたの」

ワイ「そ、そうなん・・・?」
ワイ「ま、まあ、おかげで勉強になったからええわ」
ワイ「ありがとうやで」

〜おしまい〜

Findy Engineer Labさんにも記事を寄稿しましたやで

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
What you can do with signing up
132
Help us understand the problem. What are the problem?