かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

TypeScript 配列の値がキーになる型を作りたい

環境: "typescript": "^4.4.4"

{[key in Union]: value type} で定義できる

// この配列の値がキーになるオブジェクトの型を作りたい
const luminas = ['OzoraAkari', 'HikamiSumire', 'ShinjoHinaki'] as const;

type LuminasValues = typeof luminas[number];
// => LuminasValues = type LuminasValueType = "OzoraAkari" | "HikamiSumire" | "ShinjoHinaki";

type Luminas = {
  [key in LuminasValues]: any;
}
/*
=> type Luminas = {
    OzoraAkari: any;
    HikamiSumire: any;
    ShinjoHinaki: any;
}
*/

interface だとエラーになる

[key in LuminasValues] の部分がエラーになる
A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.
A computed property name must be of type 'string', 'number', 'symbol', or 'any'.

/*
A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1169)
A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)
*/
interface ILuminas {
  [key in LuminasValues]: any;
}

Record<Keys, Type> を使うと簡単に書ける

Constructs an object type whose property keys are Keys and whose property values are Type. This utility can be used to map the properties of a type to another type.
cf. TypeScript: Documentation - Utility Types

const luminas = ['OzoraAkari', 'HikamiSumire', 'ShinjoHinaki'] as const;

type LuminasValues = typeof luminas[number];
// => LuminasValues = type LuminasValueType = "OzoraAkari" | "HikamiSumire" | "ShinjoHinaki";

type Luminas = Record<LuminasValues, any>
/*
=> type Luminas = {
    OzoraAkari: any;
    HikamiSumire: any;
    ShinjoHinaki: any;
}
*/

note. interface はプロパティを動的に定義できないケースがある

interface A {
  [key: string]: any;
}
// => string の key なんでもは OK

interface B {
  [key: string | number]: any;
}
// => key が Union 型なのも OK

interface C {
  [key: 'A' | 'B']: any;
}
// => Error
// => An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.

interface D {
  [key in 'A' | 'B']: any
}
// => Error
// => A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.

Union型を key にするのはOK だけど [key: 'A' | 'B'] のようなリテラル型のUnionでのプロパティ設定はエラーになる
An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
プロパティ決まってるなら {A: any; B: any;} のように書けって事っぽい。

[key: 'A' | 'B'] のようなリテラル型のUnionでのプロパティ設定は type でも同じエラーになる

type C = {
  [key: 'A' | 'B']: any;
}
// => An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.

type D = {
  [key in 'A' | 'B']: any;
}
// => OK

interface で定義しようとして苦しんだ。


[参考]