共通型、型ガード、型パラメータ... TypeScript インデックスシグネチャでユニオン型を使いこなす
TypeScript インデックスシグネチャのユニオン型エラーとその解決策
エラーメッセージ
An index signature parameter type cannot be a union type.
インデックスシグネチャとは
インデックスシグネチャは、オブジェクトのキーと値の型の関係を定義するものです。 例えば、以下のようなコードがあります。
interface Person {
name: string;
age: number;
}
const person: Person = {
name: "John Doe",
age: 30,
};
// 以下のように、オブジェクトのキーを使って値を取得できます。
const name = person.name; // "John Doe"
const age = person.age; // 30
この例では、Person
インターフェースは name
と age
という 2 つのプロパティを持ち、それぞれ string
型と number
型であることを定義しています。
ユニオン型とは
ユニオン型は、複数の型を組み合わせた型です。 例えば、以下のようなコードがあります。
type PersonOrAnimal = Person | Animal;
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
// 以下のように、`PersonOrAnimal` 型の変数には `Person` 型または `Animal` 型の値を代入できます。
const person: Person = personOrAnimal;
const animal: Animal = personOrAnimal;
この例では、PersonOrAnimal
型は Person
型または Animal
型の値を持つことができることを定義しています。
エラーの原因
インデックスシグネチャでユニオン型を使用しようとすると、エラーが発生します。 例えば、以下のようなコードがあります。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
type PersonOrAnimal = Person | Animal;
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
// エラーが発生します。
const name: string = personOrAnimal.name;
この例では、PersonOrAnimal
型は Person
型または Animal
型の値を持つことができます。 しかし、name
プロパティは Person
型と Animal
型で異なる型を持っています。 そのため、personOrAnimal.name
の型は string
型または string | undefined
型になります。
解決策
このエラーを解決するには、インデックスシグネチャでユニオン型を使用しないようにする必要があります。 以下のような方法で解決できます。
- 共通の型を使用する
Person
型と Animal
型に共通する型があれば、その型をインデックスシグネチャで使用できます。 例えば、以下のようなコードがあります。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
// 共通の型は "string" 型です。
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
const name: string = personOrAnimal.name; // エラーが発生しない
この例では、Person
型と Animal
型は name
プロパティを持っています。 そして、name
プロパティの型は string
型です。 そのため、personOrAnimal.name
の型は string
型になります。
- 型ガードを使用する
型ガードを使用して、インデックスシグネチャで使用する型を決定することができます。 例えば、以下のようなコードがあります。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
type PersonOrAnimal = Person | Animal;
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
// 型ガードを使用して、`Person` 型かどうかを判断します。
if (isPerson(personOrAnimal)) {
const name: string = personOrAnimal.name; // エラーが発生しない
} else {
const name: string | undefined = personOrAnimal.name; // エラーが発生しない
}
function
インデックスシグネチャでユニオン型を使用する
// インデックスシグネチャでユニオン型を使用すると、エラーが発生します。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
type PersonOrAnimal = Person | Animal;
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
// エラーが発生します。
const name: string = personOrAnimal.name;
// 共通の型を使用すると、エラーが発生しません。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
// 共通の型は "string" 型です。
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
const name: string = personOrAnimal.name; // エラーが発生しない
// 型ガードを使用して、インデックスシグネチャで使用する型を決定することができます。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
type PersonOrAnimal = Person | Animal;
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
// 型ガードを使用して、`Person` 型かどうかを判断します。
if (isPerson(personOrAnimal)) {
const name: string = personOrAnimal.name; // エラーが発生しない
} else {
const name: string | undefined = personOrAnimal.name; // エラーが発生しない
}
function isPerson(personOrAnimal: PersonOrAnimal): personOrAnimal is Person {
return (personOrAnimal as Person).age !== undefined;
}
型パラメータを使用する
// 型パラメータを使用して、インデックスシグネチャで使用する型を決定することができます。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
// 型パラメータを使用して、インデックスシグネチャで使用する型を決定します。
function getProp<T extends Person | Animal>(obj: T, key: keyof T): T[key] {
return obj[key];
}
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
const name: string = getProp(personOrAnimal, "name"); // エラーが発生しない
インデックスシグネチャを使用しない
// インデックスシグネチャを使用せずに、個別にプロパティを定義することができます。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
const person: Person = {
name: "John Doe",
age: 30,
};
const animal: Animal = {
name: "John Doe",
species: "Dog",
};
// 個別にプロパティを定義します。
const personName: string = person.name;
const animalName: string = animal.name;
keyof 演算子と in 演算子を使用する
keyof
演算子を使用して、オブジェクトのすべてのキーを取得できます。 in
演算子を使用して、キーがオブジェクトに存在するかどうかを確認できます。 以下のようなコードがあります。
interface Person {
name: string;
age: number;
}
interface Animal {
name: string;
species: string;
}
type PersonOrAnimal = Person | Animal;
const personOrAnimal: PersonOrAnimal = {
name: "John Doe",
age: 30,
};
// `keyof` 演算子を使用して、`PersonOrAnimal` 型のすべてのキーを取得します。
const keys: (keyof PersonOrAnimal)[] = ["name", "age", "species"];
// `in` 演算子を使用して、キーが `personOrAnimal` に存在するかどうかを確認します。
if ("name" in personOrAnimal) {
const name: string = personOrAnimal.name; // エラーが発生しない
}
if ("age" in personOrAnimal) {
const age: number = personOrAnimal.age; // エラーが発生しない
}
if ("species" in personOrAnimal) {
const species: string = personOrAnimal.species; // エラーが発生しない
}
discriminated unionを使用する
discriminated unionは、ユニオン型の一種で、各型に識別子プロパティを追加します。 以下のようなコードがあります。
interface Person {
type: "person";
name: string;
age: number;
}
interface Animal {
type: "animal";
name: string;
species: string;
}
type PersonOrAnimal = Person | Animal;
const personOrAnimal: PersonOrAnimal = {
type: "person",
name: "John Doe",
age: 30,
};
// 識別子プロパティを使用して、型を判断します。
switch (personOrAnimal.type) {
case "person": {
const name: string = personOrAnimal.name; // エラーが発生しない
const age: number = personOrAnimal.age; // エラーが発生しない
break;
}
case "animal": {
const name: string = personOrAnimal.name; // エラーが発生しない
const species: string = personOrAnimal.species; // エラーが発生しない
break;
}
}
型エイリアスを使用する
型エイリアスを使用して、インデックスシグネチャを持つ型を定義できます。 以下のようなコードがあります。
type Person = {
name: string;
age: number;
};
type Animal = {
name: string;
species: string;
};
type PersonOrAnimal = Person | Animal;
// 型エイリアスを使用して、インデックスシグネチャを持つ型を定義します。
type PersonOrAnimalIndex = {
[key in keyof PersonOrAnimal]: PersonOrAnimal[key];
};
const personOrAnimal: PersonOrAnimalIndex = {
name: "John Doe",
age: 30,
};
// 型エイリアスを使用して、インデックスシグネチャでアクセスできる型を定義します。
const name: string = personOrAnimal.name; // エラーが発生しない
typescript