TypeScript でオブジェクトを継承する3つの方法:スプレッド型 vs インターフェース vs クラス
TypeScript: スプレッド型はオブジェクト型からのみ作成できる
TypeScriptにおける「スプレッド型」は、既存の型を基に新しい型を定義する際に便利な機能です。しかし、重要な注意点として、スプレッド型はオブジェクト型からのみ作成できるという制約があります。
このエラーメッセージ「Typescript: Spread types may only be created from object types」は、この制約に違反していることを示しています。つまり、スプレッド型を定義しようとしている型がオブジェクト型ではないため、エラーが発生しているのです。
スプレッド型のしくみ
スプレッド型は、既存の型のプロパティを継承した新しい型を定義します。具体的には、スプレッド構文 ...
を用いて既存の型を展開することで、その型に含まれるプロパティをすべて新しい型に含めます。
type ExistingType = {
name: string;
age: number;
};
type NewType = {
...ExistingType,
address: string;
};
上記の例では、ExistingType
型のプロパティ name
と age
を NewType
型に継承し、さらに address
という新しいプロパティを追加しています。
オブジェクト型のみ許される理由
スプレッド型がオブジェクト型からのみ作成できる理由は、以下の2つが挙げられます。
- プロパティの継承: スプレッド型の目的は、既存の型のプロパティを継承することです。オブジェクト型は、プロパティを定義するための型であるため、この目的に合致します。一方、他の型 (例: プリミティブ型、関数型、ユニオン型など) はプロパティを持たないため、スプレッド型で継承する対象が存在しません。
- 型安全性: TypeScriptは、型システムによってプログラムの型安全性を保証します。スプレッド型をオブジェクト型に限定することで、継承されるプロパティの型が明確になり、型安全性を維持することができます。
解決策
このエラーを解決するには、スプレッド型を定義しようとしている型がオブジェクト型であることを確認する必要があります。具体的には、以下のいずれかの方法が考えられます。
- オブジェクト型への変換: もしスプレッド型にしたい型がオブジェクト型でない場合は、オブジェクト型に変換する必要があります。例えば、文字列型の場合はオブジェクト型
{ value: string }
に変換することができます。 - 部分型: もしスプレッド型にしたい型が部分的なオブジェクト型 (例: 一部のプロパティのみを定義した型) である場合は、部分型を使用することができます。部分型は、既存の型のプロパティのうち、必要なものだけを継承する型です。
これらの方法を踏まえ、適切な型を用いることで、エラーを解決し、スプレッド型を正しく定義することができます。
TypeScript スプレッド型 サンプルコード
基本的な例
以下の例は、Person
型と Address
型を定義し、Person
型に Address
型のプロパティをスプレッド構文を使って継承した Customer
型を定義しています。
type Person = {
name: string;
age: number;
};
type Address = {
city: string;
country: string;
};
type Customer = {
...Person,
address: Address;
};
const customer: Customer = {
name: 'John Doe',
age: 30,
city: 'New York',
country: 'USA'
};
このコードでは、customer
変数に Customer
型の値が代入されています。customer
変数には、name
、age
、city
、country
というプロパティが存在することが保証されます。
部分型の例
以下の例は、Person
型から age
プロパティを除いた部分型 PartialPerson
を定義し、Customer
型に PartialPerson
型と address
プロパティをスプレッド構文を使って継承した VIPCustomer
型を定義しています。
type Person = {
name: string;
age: number;
};
type PartialPerson = Omit<Person, 'age'>; // 'age' プロパティを除いた部分型
type Address = {
city: string;
country: string;
};
type VIPCustomer = {
...PartialPerson,
address: Address,
membershipLevel: string;
};
const vipCustomer: VIPCustomer = {
name: 'Jane Doe',
city: 'Los Angeles',
country: 'USA',
membershipLevel: 'Gold'
};
ユニオン型の例
以下の例は、Person
型と Company
型をユニオン型で定義し、Contact
型にこのユニオン型と email
プロパティをスプレッド構文を使って継承した BusinessContact
型を定義しています。
type Person = {
name: string;
age: number;
};
type Company = {
companyName: string;
industry: string;
};
type Contact = Person | Company;
type BusinessContact = {
...Contact,
email: string;
};
const businessContact: BusinessContact = {
name: 'John Doe',
email: '[email protected]',
industry: 'Tech'
};
これらの例は、TypeScriptにおけるスプレッド型の基本的な使用方法を示しています。スプレッド型を活用することで、柔軟かつ型安全なコードを記述することができます。
TypeScript スプレッド型以外にもオブジェクトを継承する方法
スプレッド型以外にも、TypeScript でオブジェクトを継承する方法はいくつかあります。それぞれの特徴と使い分けについて説明します。
インターフェースは、オブジェクトの構造を定義する型です。スプレッド型と異なり、プロパティの型だけでなく、オプションプロパティ、メソッド、インデクサーなども定義できます。
interface Person {
name: string;
age: number;
}
interface Customer extends Person {
address: Address;
}
type Address = {
city: string;
country: string;
};
const customer: Customer = {
name: 'John Doe',
age: 30,
city: 'New York',
country: 'USA'
};
クラスは、オブジェクトのテンプレートを定義する型です。インターフェースと同様に、プロパティ、メソッド、インデクサーなどを定義できます。さらに、継承、多重継承、ポリモーフィズムなどの機能も利用できます。
class Person {
constructor(public name: string, public age: number) {}
}
class Customer extends Person {
constructor(public name: string, public age: number, public address: Address) {
super(name, age);
}
}
type Address = {
city: string;
country: string;
};
const customer = new Customer('John Doe', 30, { city: 'New York', country: 'USA' });
ジェネリック型は、型パラメータを用いて汎用的な型を定義する型です。オブジェクト型だけでなく、他の型も受け取ることができます。
type WithAddress<T> = {
address: Address;
} & T;
type Customer = WithAddress<Person>;
const customer: Customer = {
name: 'John Doe',
age: 30,
city: 'New York',
country: 'USA'
};
型エイリアスは、既存の型に新しい名前を付けるための型です。スプレッド型と異なり、型構造を変更することはできません。
type Customer = {
name: string;
age: number;
address: Address;
};
const customer: Customer = {
name: 'John Doe',
age: 30,
city: 'New York',
country: 'USA'
};
使い分け
- シンプルさ: シンプルなオブジェクト継承には、スプレッド型または型エイリアスが適しています。
- 機能: プロパティの型だけでなく、オプションプロパティ、メソッド、インデクサーなども定義したい場合は、インターフェースまたはクラスが適しています。
- 汎用性: 汎用的な型を定義したい場合は、ジェネリック型が適しています。
スプレッド型以外にも、TypeScript でオブジェクトを継承する方法はいくつかあります。それぞれの方法の特徴と使い分けを理解し、適切な方法を選択することで、より柔軟かつ型安全なコードを記述することができます。
typescript