70009


ひとつの配列の中に複数のジェネリクスを持つ配列型を定義したいです。

達成したいことは下記コードのようなケースです。

[code]

    type BlockType = "hasNumber" | "hasBoolean";
    
    type NumberAttribute = {
      numberProp: number;
    }
    
    type BooleanAttribute = {
      booleanProp: boolean;
    }
    
    type Attribute<T extends BlockType> =
      T extends "hasNumber" ? NumberAttribute :
      T extends "hasBoolean" ? BooleanAttribute :
      never;
    
    type Block<T extends BlockType> = {
      id: string;
      type: T;
      attribute: Attribute<T>;
    }
    
    type BlockList = Block<"hasNumber" | "hasBoolean">[];
    
    const list: BlockList = [
      {
        id: "id001",
        type: "hasNumber", // type が決まれば
        attribute: { numberProp: 0 }, // attribute も決まる
      },
      {
        id: "id002",
        type: "hasBoolean", // 同一配列内に異なる type を含むことができる
        attribute: { booleanProp: false },
      },
      {
        id: "id003",
        type: "hasNumber", // type === "hasNumber" のときは
        attribute: { booleanProp: false }, // attibute.booleanProp をコンパイルエラーにしたい(現状はエラーにならない)
      },
    ]
    
[/code]

`BlockType` はあとから増える可能性があり、その `BlockType` によって `Attribute` の型が変わります。  
`Block`は `id`, `type`, `attribute` を持つことは変わりません。

ソースコード上のコメントにもある通り、 `type` の値によって `attribute` の型が確定できるようにしたいです。

`type BlockList = Block<"hasNumber" | "hasBoolean">[];`
のような指定では目的の仕様は達成できませんでした。

よろしくお願いします。


CC BY-SA 4.0
2020-08-31T05:27:10.410
70008
2020-08-31T07:22:33.127
2020-08-31T07:22:33.127
2238
41710
question
typescript
任意のジェネリクスを受け取る配列の型の付け方


`Block`と`BlockList`を次のように書き換えれば目的の仕様になります。

[code]

    type SingleTypeBlock<T extends BlockType> = {
      id: string;
      type: T;
      attribute: Attribute<T>;
    }
    type GetBlock<T extends BlockType> = T extends BlockType ? SingleTypeBlock<T> : never;
    
    type Block = GetBlock<BlockType>;
    
    type BlockList = Block[];
    
[/code]

質問者さんがお書きになった`Block<T>`型は`SingleTypeBlock<T>`と改名しました。  
実は、今回欲しいのは`SingleTypeBlock<"hasNumber"> |
SingleTypeBlock<"hasBoolean">`というユニオン型です。この定義にすれば、`list`の3つ目のようなちぐはぐな値を弾くことができます。  
しかし、手動で`SingleTypeBlock<"hasNumber"> |
SingleTypeBlock<"hasBoolean">`と書くのは`BlockType`があとから増えた際に大変ですから、`BlockType`が増えれば自動的にユニオン型の構成要素が増えるようにしたいです。  
それを担うのが`GetBlock<BlockType>`です。これはconditional typeのunion
distributionという挙動を利用して、`BlockType`の構成要素の各`T`に対して`SingleTypeBlock<T>`を計算し、それらのユニオン型をとるようになっています。  
よって、`GetBlock<BlockType>`が`SingleTypeBlock<"hasNumber"> |
SingleTypeBlock<"hasBoolean">`に計算されます。


CC BY-SA 4.0
2020-08-31T06:00:41.433
70009
2020-08-31T06:00:41.433
30079
70008
answer