典型的な型の型を実装する


あなたは、あなたがAを説明しようとしていたあなたのtypescript codebaseでケースに遭遇しましたoneOf ?
私は複数回、以前は、読みやすさとタイプの安全性を選択する必要がありました😑.
私たちは、私たちのゲームのプレーヤーは、冒険の前に動物が彼と一緒に選択することができるという事実を表現したいとしましょう.しかし、ゲームで利用可能なすべての動物は、彼は1つだけを選択する必要があります:
  • 犬🐶
  • オオカミ🐺
  • ワシ🦅
  • ネズミ🐭
  • タイプ安全性の読みやすさ


    タイプセーフより読みやすくするには、以下のように動物とプレイヤーを簡単に説明できます.
    interface Animal {
      id: string;
      name: string;
    
      // the animal can be ONE of the following
      dog: Dog | null;
      wolf: Wolf | null;
      eagle: Eagle | null;
      mouse: Mouse | null;
    }
    
    interface Player {
      id: string;
      nickname: string;
      level: number;
      animal: Animal;
    }
    
    この場合、インターフェイスはかなり読みやすくなりますがdogwolf プレーヤーとtypescriptがまだ幸せです.

    読みやすい安全性


    我々は、プレイヤーが🐶, 🐺, 🦅 and 🐭. 我々は、その情報を活用する代わりにコメントをすることができますて// the animal can be ONE of the following , 私たちはtypescriptでそれを表現することができます(それは後で私たちに後で追加安全性を持ってきます)!
    物事を簡単にするためには、2 :🐶, 🐺.
    type Animal = {
      id: string;
      name: string;
    } & (
      | {
          dog: Dog | null;
          wolf: null;
        }
      | {
          dog: null;
          wolf: Wolf | null;
        });
    
    interface Player {
      // ... same ...
    }
    
    このように、我々は動物が犬かオオカミでありえるという事実を表します、しかし、我々は同時に両方を持つことができません.上記の型を持つのは妥当であるかもしれませんが、現在4匹の動物とどのように見えるかを見てみましょう.
    type Animal = {
      id: string;
      name: string;
    } & ({
      dog: Dog | null;
      wolf: null;
      eagle: null;
      mouse: null;
    } | {
      dog:  null;
      wolf: Wolf | null;
      eagle: null;
      mouse: null;
    } | {
      dog: Dog null;
      wolf: Wolf null;
      eagle: Eagle | null;
      mouse: null;
    } | {
      dog: null;
      wolf: null;
      eagle: null;
      mouse: Mouse | null;
    });
    
    interface Player {
      // ... same ...
    }
    
    UH😵... 今、我々は5、10または15の新しい動物を追加するには、ゲームに更新を行った場合それはどのように見えるか想像できますか?🤕
    これは単にスケールしません.

    両世界のベスト


    どのようにクールには次のようなものがあります.
    type Animal = {
      id: string;
      name: string;
    } & OneOf<{
      dog: Dog;
      wolf: Wolf;
      eagle: Eagle;
      mouse: Mouse;
    }>;
    
    interface Player {
      // ... same ...
    }
    
    前に例として、すべてのタイプセーフティがあったと仮定すると、私はそれはかなり良いだろうと思います!
    しかし、我々は?一歩一歩やりましょう.
    最初にすることは、我々が通るならば、一般的なタイプを持つことです
    {
      dog: Dog;
      wolf: Wolf;
      eagle: Eagle;
      mouse: Mouse;
    }
    
    そして、上記のタイプからの1つのキーdog 次に、次のように入力します.
    {
      dog: Dog;
      wolf: Wolf | null;
      eagle: Eagle | null;
      mouse: Mouse | null;
    }
    
    このタイプに電話しますOneOnly :
    type OneOnly<Obj, Key extends keyof Obj> = { [key in Exclude<keyof Obj, Key>]: null } & Pick<Obj, Key>;
    
    破壊して、上記のタイプを理解しようとしましょう.
    type OneOnly<Obj, Key extends keyof Obj>
    
    今のところ、それはすばらしいはずです.我々は、我々が呼ぶタイプを通過することができますObj そして、その型の1つのキーを2番目の引数として渡す必要があります.
    { [key in Exclude<keyof Obj, Key>]: null }
    
    今、我々はループのすべてのキーをループObj を返します.これらのキーのすべてのために、我々は彼らが受け入れることができる唯一の価値があると言いますnull .
    パズルの最後の欠落部分:
    Pick<Obj, Key>
    
    から抽出するObj タイプKey .
    だから最後に我々は持っている
    type OneOnly<Obj, Key extends keyof Obj> = { [key in Exclude<keyof Obj, Key>]: null } & Pick<Obj, Key>;
    
    というのは、dog 例えば、
    type OnlyDog = OneOnly<
      {
        dog: Dog;
        wolf: Wolf | null;
        eagle: Eagle | null;
        mouse: Mouse | null;
      },
      'dog'
    >;
    
    OnlyDog 型は以下のようになります:
    {
      dog: Dog;
      wolf: Wolf | null;
      eagle: Eagle | null;
      mouse: Mouse | null;
    }
    
    クール!我々は、我々の決勝の最も複雑な部分を持っていると思いますOneOf 型付け✅.
    今、あなたはこれが来るのを見たかもしれません..我々は、我々の上でループしなければなりませんObj すべてのOneOnly キーの種類
    type OneOfByKey<T> = { [key in keyof T]: OneOnly<T, key> };
    
    ここであまりにも空想は、まだ、この行はかなり強力です!我々がそうするならば
    type OnlyOneAnimal = OneOfByKey<{
      dog: Dog;
      wolf: Wolf;
      eagle: Eagle;
      mouse: Mouse;
    }>;
    
    次のように入力します.
    {
      dog: OneOnly<Dog>;
      wolf: OneOnly<Wolf>;
      eagle: OneOnly<Eagle>;
      mouse: OneOnly<Mouse>;
    }
    
    それはsuuuuuuper coooool右ですか?でも.🤔
    上記の型からすべての値のunion型が欲しい.
    以下のようになります.
    OneOnly<Dog> | OneOnly<Wolf> | OneOnly<Eagle> | OneOnly<Mouse>
    
    どのように我々はそれを達成することができますか?単純に次のようにします.
    type ValueOf<Obj> = Obj[keyof Obj];
    
    これは、Obj 種類
    最後にOneOf では、
    type OneOfType<T> = ValueOf<OneOfByKey<T>>;
    
    🤩 いいね.🤩
    要約として完全なコードを示します.
    type ValueOf<Obj> = Obj[keyof Obj];
    type OneOnly<Obj, Key extends keyof Obj> = { [key in Exclude<keyof Obj, Key>]: null } & Pick<Obj, Key>;
    type OneOfByKey<Obj> = { [key in keyof Obj]: OneOnly<Obj, key> };
    export type OneOfType<Obj> = ValueOf<OneOfByKey<Obj>>;
    
    そして、どうやって使うことができますか
    type Animal = {
      id: string;
      name: string;
    } & OneOf<{
      dog: Dog;
      wolf: Wolf;
      eagle: Eagle;
      mouse: Mouse;
    }>;
    
    interface Player {
      // ... same ...
    }
    
    クールなことは、プレイヤーとその動物を定義するとき、我々は動物の1つを定義することができます.0ではなく2ではなく3で、4ではありません.つだけ😁.
    はい、どうぞTypescript Playground あなたは楽しいことができます.これは、我々が見てきたすべてのコードと最後のデモ上記のGIFで示されています.

    タイポを発見?


    あなたがtypo、改善されることができた文またはこのブログ柱で更新されるべき何かを見つけたならば、あなたはそれをGITリポジトリでそれにアクセスして、プル要求をすることができます.コメントを投稿するのではなく、直接あなたの変更で新しいプル要求を行ってください.