(最終更新月:2023年12月)
✔当記事は以下のような疑問を持つ方に最適です
「TypeScriptをReactプロジェクトで使いたいけど、どう始めればいいの?」
「TypeScriptとReactの組み合わせ方が知りたい」
「TypeScriptでReactコンポーネントを書く具体的な例が見たい」
✔当記事を通じて提供する情報
- ReactプロジェクトにTypeScriptを導入する方法
- 実践的なTypeScriptとReactのコーディングテクニック
- TypeScriptを利用したReactコンポーネントの実践例
当記事では、TypeScriptをReact開発に取り入れる初歩から応用技まで、実際のコード例を交えながら透徹したガイダンスを提供しています。
最終行まで是非ともお読みいただき、TypeScriptとReactでのクリアなコーディングパスを見出してください。
環境セットアップ
まずは、環境のセットアップ方法について詳しく見ていきましょう。
最初に適切なツールと環境をセットアップすることで、開発プロセスがスムーズに進みます。
- 必要なツールのセットアップ方法
- 実行可能な環境の作成
- create-react-appのTypeScriptテンプレート使用
必要なツールのセットアップ方法
Webアプリケーション開発には複数のツールが必要です。
まずは、Node.jsをインストールし、npmまたはYarnといったパッケージマネージャーをセットアップしましょう。
次に、TypeScriptのコンパイラであるtsc
をグローバルにインストールします。
最後に、コード編集のためのIDE(例:Visual Studio Code)をインストールしておきましょう。
npm install -g typescript
実行可能な環境の作成
ツールのセットアップが完了したら、プロジェクトフォルダを作成し、依存関係を初期化します。
以下のコマンドを使って、npmかYarnで新規プロジェクトを始めましょう。
mkdir my-react-app
cd my-react-app
npm init -y
この時点で、基本的なファイル構造が確立し、開発を始める準備が整いました。
create-react-appのTypeScriptテンプレート使用
create-react-app
はReactプロジェクトの立ち上げを簡単にするツールです。
TypeScriptテンプレートを指定して新しいプロジェクトを始めるには、次のコマンドを実行します。
npx create-react-app my-app --template typescript
Reactに最適化されたTypeScriptのセットアップを含んだ新しいReactアプリケーションを生成します。
プロジェクトの概要
こちらでは、プロジェクト概要として、要点を深掘りしていきます。
ひとつずつのステップを踏んでいくことで、プロジェクトの全体像を理解し、計画的な開発を進めていけるでしょう。
- アプリケーションの構成要素
- スターターコードの概観
- アプリケーション設計の初期思考
アプリケーションの構成要素
アプリケーションを構築する上で、まず明確にしておくべきは、それが何から成り立っているかです。
主に、以下の3要素に注目しましょう。
- ユーザーインターフェース(UI)を構成するコンポーネント群
- アプリケーションの状態を管理するロジック
- データを処理するサービスやAPI
スターターコードの概観
create-react-app
で生成されるスターターコードには、アプリケーションの基本構造が含まれています。
src
ディレクトリ下にあるApp.tsx
やindex.tsx
などのファイルは、最初に慣れ親しむべき重要な部分です。
これらのファイルを見ることで、Reactコンポーネントの基本形式やアプリケーションがどのように組み立てられているのかを理解できます。
アプリケーション設計の初期思考
実際にコードを書き始める前に、アプリケーションの設計についての初期的な思考が必要です。
アプリケーションの規模に関わらず重要なポイントを検討しましょう。
- どのような機能が必要か
- それにどのようなデータ構造が適しているか
- どのようにコンポーネントを分割して再利用可能にするか
ReactとTypeScriptの基礎
ここでは、ReactとTypeScriptの基礎知識を踏まえ、いくつかの重要なトピックにフォーカスして見ていきましょう。
これらを理解することで、型安全性を提供しながら、Reactの実践的なコーディングを身につけられます。
- コンポーネントとpropsの型定義
- コンポーネントの状態管理(Stateの導入)
- イベントハンドラと型付け
コンポーネントとpropsの型定義
Reactコンポーネントにはさまざまなpropsを渡すことができますが、TypeScriptを使うとこれらのpropsに型を定義できます。
以下のコードは、Greeting
コンポーネントに対してname
というpropsを受け取る際に、Props
という型を定義する例です。
type Props = {
name: string;
};
const Greeting: React.FC<Props> = ({ name }) => <h1>Hello, {name}!</h1>;
この型定義により、予期しない型のpropsが渡されたときに、コンパイル時にエラーが発生し、バグの早期発見につながります。
コンポーネントの状態管理(Stateの導入)
Reactでは、コンポーネントの状態(state)を管理することが重要です。
TypeScriptを使用することで、stateの型を定義し、型安全性を確保できます。
以下に、useState
フックを使って状態を持つ単純なカウンターコンポーネントの例を示します。
const Counter: React.FC = () => {
const [count, setCount] = useState<number>(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
このコードではcount
の状態をnumber
型として初期化し、型安全な操作を保証しています。
イベントハンドラと型付け
イベントハンドリングをする際にも、TypeScriptは効果を発揮します。
以下は、イベントハンドラの型付けをおこなったフォーム送信の例です。
const Form: React.FC = () => {
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
// Form submission logic
};
return (
<form onSubmit={handleSubmit}>
{/* Form elements */}
<button type="submit">Submit</button>
</form>
);
};
このイベントハンドラでは、イベントの型をReact.FormEvent<HTMLFormElement>
と指定することで、型安全にイベントオブジェクトを扱えます。
コンポーネントの実装と連携
コンポーネントの実装では、コンポーネント間の連携について深堀りします。
ここで、単一のコンポーネントを超えた範囲で型を活かす方法も学びましょう。
- 複数コンポーネントのレンダリング
- インタラクティブなUIの作成
- コンポーネント間での状態のリフトアップ
複数コンポーネントのレンダリング
Reactアプリケーションは通常、多くのコンポーネントによって構成され、それぞれが分かりやすい機能を持っています。
以下は、UserList
コンポーネントにUserListItem
コンポーネントを複数組み込む例です。
type User = {
id: number;
name: string;
};
type UserListItemProps = {
user: User;
};
const UserListItem: React.FC<UserListItemProps> = ({ user }) => (
<li>{user.name}</li>
);
type UserListProps = {
users: User[];
};
const UserList: React.FC<UserListProps> = ({ users }) => (
<ul>{users.map(user => <UserListItem key={user.id} user={user} />)}</ul>
);
このパターンによって、データをもとに一覧表示する際の型安全性が保証され、データの構造がコード全体で一貫しています。
インタラクティブなUIの作成
インタラクティブなUIを作成するには、ユーザーの操作に応じた状態の変化をコンポーネントが捉えることが必要です。
以下に、シンプルなトグルスイッチのコンポーネント例を示します。
const ToggleSwitch: React.FC = () => {
const [isOn, setIsOn] = useState<boolean>(false);
const toggle = () => setIsOn(!isOn);
return (
<button onClick={toggle}>{isOn ? 'ON' : 'OFF'}</button>
);
};
このような単純なインタラクションのコンポーネントでも、TypeScriptを使って型安全に状態の変化を管理することができます。
コンポーネント間での状態のリフトアップ
ひとつの状態を複数のコンポーネントで共有する必要が生じる場合、“リフトアップ(Lifting State Up)”というパターンを使用します。
以下は、2つのコンポーネントで共有するカウンターの状態を親コンポーネントにリフトアップした例です。
type CounterProps = {
value: number;
onIncrement: () => void;
};
const CounterButton: React.FC<CounterProps> = ({ value, onIncrement }) => (
<button onClick={onIncrement}>{value}</button>
);
const CounterDisplay: React.FC<CounterProps> = ({ value }) => (
<p>Count: {value}</p>
);
const CounterApp: React.FC = () => {
const [count, setCount] = useState<number>(0);
const increment = () => setCount(count + 1);
return (
<div>
<CounterDisplay value={count} onIncrement={increment} />
<CounterButton value={count} onIncrement={increment} />
</div>
);
};
この例では、CounterDisplay
とCounterButton
は同じ状態を共有しており、それらの状態はCounterApp
によって管理されています。
コンポーネント設計の工夫
コンポーネント設計では、より洗練されたコンポーネント設計について見ていきましょう。
ここでは、コンポーネント間の関連する型をどうまくマッチした設計と型付けを行う方法を見ていきます。
- コンポーネント間のデータフロー
- 関数コンポーネントとHooksの採用
- 使い易いコンポーネントのためのベストプラクティス
コンポーネント間のデータフロー
Reactで効果的にアプリケーションを構築するには、コンポーネント間でのデータフローを適切に設計することが欠かせません。
データは主に親から子へprops経由で渡され、また、コールバック関数を使って子から親への情報伝達が可能です。
TypeScriptを使うことで、渡されるデータと関数の型が予め定義されるため、エラーを防ぎながら予想通りのデータフローを確実に実現できます。
以下の例では、子コンポーネントChild
にて、親コンポーネントParent
から渡されたデータとコールバック関数を型安全に使用しています。
type ChildProps = {
data: string;
onDataChange: (newData: string) => void;
};
const Child: React.FC<ChildProps> = ({ data, onDataChange }) => {
return (
<div>
<span>{data}</span>
<button onClick={() => onDataChange('Updated Data')}>Update</button>
</div>
);
};
const Parent: React.FC = () => {
const [data, setData] = useState<string>('Initial Data');
return (
<Child data={data} onDataChange={setData} />
);
};
このパターンは、全体的に型安全で一貫したアプリケーションを構築するための基盤となります。
関数コンポーネントとHooksの採用
関数コンポーネントとHooksは、再利用可能で分離されたロジックを持つコンポーネントを設計する際に便利です。
関数コンポーネントを用いることで、クラスベースのコンポーネントよりも簡潔で読みやすいコンポーネントを作成可能。
また、Stateやライフサイクルなどの機能をフックを通じて関数コンポーネントに接続できます。
以下は、カスタムフックを使用した例です。
function useToggle(initialValue: boolean): [boolean, () => void] {
const [value, setValue] = useState<boolean>(initialValue);
const toggle = () => setValue(!value);
return [value, toggle];
}
const ToggleComponent: React.FC = () => {
const [isOn, toggleIsOn] = useToggle(false);
return (
<button onClick={toggleIsOn}>
{isOn ? 'ON' : 'OFF'}
</button>
);
};
この例では、カスタムフックuseToggle
を用いてオンオフ状態のロジックを関数コンポーネントから分離しています。
使い易いコンポーネントのためのベストプラクティス
コンポーネントをより使いやすくするためには、コードの予測可能性と再利用性を重視する必要があります。
コンポーネントのprops
にはデフォルト値を設定し、省略可能なprops
に対応可能です。
また明確なprops
の型定義により、使用する側に期待される振る舞いを説明すできます。
また、複合コンポーネントパターンやコンテキストAPIを使用して、コンポーネント間の関係を分かりやすく保持します。
以下は、デフォルトpropsを使用した例です。
type ButtonProps = {
label: string;
onClick?: () => void; // Optional prop
};
const Button: React.FC<ButtonProps> = ({ label, onClick = () => {} }) => (
<button onClick={onClick}>{label}</button>
);
このButton
コンポーネントでは、onClick
が提供されなかった場合には、何もしない関数がデフォルト値として使用されるため、型安全に取り扱えます。
関数コンポーネントとHooks
関数コンポーネントとHooksを詳しく見ていくことで、クラスコンポーネントへの依存を減らし、より宣言的で副作用の少ないコンポーネント設計が可能です。
また、コンポーネントのロジックをカスタムフックとして抽出することで、ビジネスロジックをより再利用しやすく分離できます。
- クラスから関数コンポーネントへの移行
- サードパーティライブラリとの連携
- カスタムHooksの作成
クラスから関数コンポーネントへの移行
クラスコンポーネントから関数コンポーネントへの移行は、Reactフックの導入によって一層容易になりました。
以下に、クラスコンポーネントの例と、それを関数コンポーネントとHooksを使った例に書き換えたコードを示します。
// クラスコンポーネント
class ClassCounter extends React.Component {
state = { count: 0 };
incrementCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.incrementCount}>Increment</button>
</div>
);
}
}
//関数コンポーネントとHooks
const FunctionalCounter: React.FC = () => {
const [count, setCount] = useState(0);
const incrementCount = () => setCount(count + 1);
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment</button>
</div>
);
};
このように書き換えることで、よりシンプルかつモダンなコンポーネント定義が可能になります。
まとめ
当記事でお伝えした内容は以下のとおり。
- Reactの言語にTypeScriptをセットアップする方法
- アプリケーション設計
- コンポーネント設計
Reactの開発で、TypeScriptを使うと、未然にエラーを防げます。
覚えるまでは手間もかかりますが、長期的に見れば開発時間の削減につながるでしょう。
ぜひ手を動かしながら学んでみてください。