TypeScriptでは型定義を一つのファイルで作成して、複数のファイルで共有することができます。
そのためには型をまとめたファイルを作成し、他のファイルでその型をインポートして
使用します。
親子のコンポーネントで型定義ファイルを呼び出すなどすると便利です。
以下にその手順を説明します。
1. 型定義ファイルを作成する
まず、型定義ファイル(通常types.tsやinterfaces.tsなどと名付けます)を作成します。
このファイルには、他のファイルで使用するすべての型を定義します。
types.ts(型定義ファイル)
// types.ts
// User型
export type User = {
id: number;
name: string;
email: string;
};
// Status型
export type Status = "loading" | "success" | "error";
exportキーワードを使って型をエクスポートすることで、他のファイルでインポートして
使用できるようになります。
2. 型定義を他のファイルでインポートして使用する
次に、types.tsで定義した型を他のファイルでインポートし、useStateの型注釈や
コンポーネントのProps型として使用します。
ParentComponent.tsx
import React, { useState } from "react";
import { User, Status } from "./types"; // types.ts からインポート
const ParentComponent = () => {
const [user, setUser] = useState<User | null>(null); // User型を利用
const [status, setStatus] = useState<Status>("loading"); // Status型を利用
return (
<div>
<p>Status: {status}</p>
<p>User: {user ? user.name : "No user"}</p>
</div>
);
};
export default ParentComponent;
ChildComponent.tsx
import React from "react";
import { User, Status } from "./types"; // types.ts からインポート
type ChildComponentProps = {
user: User | null;
status: Status;
setUser: React.Dispatch<React.SetStateAction<User | null>>;
setStatus: React.Dispatch<React.SetStateAction<Status>>;
};
const ChildComponent: React.FC<ChildComponentProps> = ({ user, status, setUser, setStatus }) => {
return (
<div>
<p>Status: {status}</p>
<p>User Name: {user ? user.name : "No user"}</p>
<button onClick={() => setStatus("success")}>Set Success</button>
<button onClick={() => setUser({ id: 1, name: "New User", email: "newuser@example.com" })}>
Set User
</button>
</div>
);
};
export default ChildComponent;
3. 複数のファイルで型を共有する利点
- メンテナンス性向上: 型定義が一か所にまとまるため、変更があっても
型定義ファイルのみ更新すればよいです。 - 型の再利用: 他のコンポーネントやファイルでも同じ型を再利用でき、
一貫性を保つことができます。 - コードの簡潔化: 各ファイルで共通の型を宣言する必要がなくなるため、
コードが簡潔になります。
この方法により、types.tsなどのファイルで型を定義し、複数のコンポーネントやファイル間
で簡単に型を共有できます。
ついでにinterfaceとtypeの使い分けの話。
interface と type の使い分けは、コードのスタイルや設計に大きく関わります。
TypeScriptの型システムで提供される interface と type は、
どちらもオブジェクトの型定義に使えますが、それぞれの特徴を理解することで、
使い分けを適切に行うことができます。
以下に、それぞれの特徴と使い分けについて説明します。
interface の特徴
- 拡張性(継承)が高い:
interfaceは、オブジェクト型の宣言に特化しており、
複数のインターフェースをextendsすることで型を拡張できます。これにより、
特定の型に新しいプロパティを追加しやすくなります。 - マージ可能: 同じ名前の
interfaceが複数宣言された場合、それらは自動的に
マージされます。これにより、異なるモジュールやファイルで型を追加
していくことが可能です。 - オブジェクトの形を定義するために最適: 特にオブジェクトの
形状(プロパティとその型)を定義する場合に最も使われます。
例:
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: number;
}
const employee: Employee = {
name: "John",
age: 30,
employeeId: 1234
};
type の特徴
- ユニオン型や交差型も表現可能:
typeは、オブジェクト型だけでなく、
ユニオン型、交差型、プリミティブ型など、より柔軟な型定義に使えます。
このため、複雑な型の操作を行う場合にはtypeが便利です。 - 拡張性が低い:
interfaceのように型を拡張する機能は少し制限されていますが、
交差型(&)を使うことで型を合成することができます。 - マージ不可: 同じ名前の
typeを再定義することはできず、競合してしまいます。
例:
type Person = {
name: string;
age: number;
};
type Employee = Person & {
employeeId: number;
};
const employee: Employee = {
name: "John",
age: 30,
employeeId: 1234
};
使い分けの指針
- オブジェクトの型定義は
interfaceを優先: オブジェクトの形(プロパティやメソッド)
を定義する際は、interfaceを使うのが一般的です。特に、拡張したり、
ライブラリや他の部分で後からマージする可能性がある場合は、interfaceを選ぶと良いでしょう。 - 複雑な型操作には
typeを使う: ユニオン型、交差型、あるいはプリミティブ型の
別名を定義したい場合はtypeを使うことが多いです。typeは型定義の範囲が広いため、
オブジェクト以外の型や、より柔軟な型を扱う際にはこちらを使用します。 - ライブラリのAPIとの互換性: 一部のライブラリでは
interfaceを使用していることが
多いので、APIや既存の型定義と統一するためにinterfaceを選ぶことがあるかも
しれません。
具体例:Reactのコンポーネントでの使い方
interface を使った Props の定義
interface ButtonProps {
label: string;
onClick: () => void;
}
const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
<button onClick={onClick}>{label}</button>
);
type を使った Props の定義
type ButtonProps = {
label: string;
onClick: () => void;
};
const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
<button onClick={onClick}>{label}</button>
);
この場合、どちらでも機能的には問題ありませんが、拡張性や既存のプロジェクトの
スタイルに合わせて使い分けます。
結論
interfaceを使うと、オブジェクトの型定義や型の拡張が自然で便利です。
特に、ReactコンポーネントのPropsや他のオブジェクト型の定義に適しています。typeは、柔軟な型操作(ユニオン型や交差型など)に強く、オブジェクト型に
限らない場合や、複雑な型を定義する場合に使います。
多くの場合は interface を使用し、特殊な型操作が必要な場合に type を使うというのが、
一般的な使い分けの指針です。


