「TypeScriptを勉強し始めたけどReactでどのように書くのかまではイメージできていない」
「ReactプロジェクトでTypeScriptを導入する方法を知りたい」
「フォーム機能の引数eについての型指定がよくわからない」
今回はReactプロジェクトでTypeScriptを使ったコーディングをする方法について初心者向けに解説します。
この記事では、ReactとTypeScriptの環境構築から、propsや関数の書き方を詳しく説明していきます。
propsの型付けや関数の引数・戻り値の型指定、またフォーム機能で使うe(イベント)に対する型指定まで紹介しますので、最低限ReactでTypeScriptを使う手順がイメージできるようになるはずです。
基本的にはReactとJavaScriptの基本がわかっていれば、理解できるものばかりで「あくまでReactの応用編」くらいなので安心して学んでいってください。
また動画でも解説しているので必要に応じて活用してください。
TypeScriptを使ったReactでのpropsの書き方
まずはpropsの書き方になります。
例題としてTODOリストのアプリを作ることを想定してコンポーネントを書いていきましょう。
import "./App.css";
import TaskList from "./screens/ts-props/TaskList";
function App() {
return (
<>
<TaskList />
</>
);
}
export default App;
import React from "react";
import Task from "./Task";
const TaskList = () => {
return (
<div>
<Task date="20230601" title="aaa" />
<Task date="20230602" title="bbb" />
<Task date="20230603" title="ccc" />
</div>
);
};
export default TaskList;
import React from "react";
const Task = (props) => {
return (
<div>
<p>{props.date}</p>
<p>{props.title}</p>
</div>
);
};
export default Task;
TODOリストを画面に表示するための親コンポーネントでTaskList.tsxがあり、1個のTODOを子コンポーネントのTask.tsxで用意しています。
propsを親→子に渡した場合、子コンポーネントの引数にpropsを書くのですが厳密にはpropsはオブジェクトになっていてプロパティごとに値が入ることになります。
上記の例で言うとdateとtitleという2つのプロパティに値が代入されています。
TypeScriptではそれぞれのプロパティに対して型を指定します。
import React from "react";
// ここを修正
const Task = (props: { date: string; title: string }) => {
return (
<div>
<p>{props.date}</p>
<p>{props.title}</p>
</div>
);
};
export default Task;
さらに現状だとpropsに値を代入するのを固定の値を入力していますが、繰り返し処理など固定の値を書けない場合は親コンポーネントの側でも型の指定が必要になります。
例えばAPIからTODOのデータを取得して画面に表示する方法に変えてみます。
// ここを追加
import React, { useEffect, useState } from "react";
import Task from "./Task";
const TaskList = () => {
// ここを追加
const [todos, setTodos] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/"
);
const json = await response.json();
setTodos(json);
} catch (error) {
console.error(error);
}
};
fetchData();
}, []);
return (
<div>
{/* ここを変更 */}
{todos.map((todo: { id: number; title: string }) => (
<Task key={todo.id} {...todo} />
))}
</div>
);
};
export default TaskList;
あとAPIから使用するプロパティはtitleのみを子コンポーネントにpropsとして渡すように変更しますので、子コンポーネントのTask.tsx側でdateは削除しておいてください。
import React from "react";
// dateを削除した
const Task = (props: { title: string }) => {
return (
<div>
{/* dateを削除した */}
<p>{props.title}</p>
</div>
);
};
export default Task;
APIはjsonplaceholderを使用しています。
https://jsonplaceholder.typicode.com/
APIで取得したデータはmapメソッドを使ってreturn文のなかに書いていきます。
mapメソッドの引数にはAPIから取得したデータが格納されたステート変数を書くわけですが、このmapメソッドの引数について型の指定を書いておくことになります。
そうすることで固定の値じゃない場合も正しくpropsを渡すことが可能になります。
「親でも子でも型を書いて2度手間だな」と思われた方がいるかもしれません。
実務ではReactプロジェクトのなかで何回も使う型のパターンは専用のファイルに分けて使い回すことが多いです。
srcフォルダ直下に新しくtypesフォルダを作り、そのなかにtypes.tsというファイルを新規で作成します。
- node_modules
- public
- src
- types
- types.ts
- App.tsx
- App.css
...そのほかコンポーネントのフォルダなど
先ほどのpropsの型をtypes.tsに改めて書きます。
export type TodoProps = { id: number; title: string };
types.tsにpropsの型ルールをtypeで書いておきエクスポートすることで各コンポーネントで必要に応じて使い回す流れです。
propsの型を直接書くのではなくtypes.tsからインポートする書き方に変えてみましょう。
import React, { useEffect, useState } from "react";
import Task from "./Task";
// ここを追加
import { TodoProps } from "../../types/types";
const TaskList = () => {
const [todos, setTodos] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/"
);
const json = await response.json();
setTodos(json);
} catch (error) {
console.error(error);
}
};
fetchData();
}, []);
return (
<div>
{/* ここを変更 */}
{todos.map((todo: TodoProps) => (
<Task key={todo.id} {...todo} />
))}
</div>
);
};
export default TaskList;
import React from "react";
// ここを追加
import { TodoProps } from "../../types/types";
// ここを変更
const Task = (props: TodoProps) => {
return (
<div>
<p>{props.title}</p>
</div>
);
};
export default Task;
ちなみに親コンポーネントのTaskList.tsx側でmapメソッドの引数に型を指定していますが、今回の場合はuseStateを使っていて初期値に型を書いてもOKです。
import React, { useEffect, useState } from "react";
import Task from "./Task";
import { TodoProps } from "../../types/types";
const TaskList = () => {
// ここに型指定を書いた
const [todos, setTodos] = useState<TodoProps[]>([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/"
);
const json = await response.json();
setTodos(json);
} catch (error) {
console.error(error);
}
};
fetchData();
}, []);
return (
<div>
{/* ここから型指定を削除した */}
{todos.map(todo) => (
<Task key={todo.id} {...todo} />
))}
</div>
);
};
export default TaskList;
さらに子コンポーネントのTask.tsx側でpropsを受け取る際に、propsと書かずに分割代入でプロパティ名だけ取り出す書き方もOKです。
import React from "react";
// ここを追加
import { TodoProps } from "../../types/types";
// 分割代入に変更
const Task = ({ title }: TodoProps) => {
return (
<div>
{/* propsを削除できる */}
<p>{title}</p>
</div>
);
};
export default Task;
画面の表示内容は変わらないのですが、少しでも効率的にコードを書くためにいろんな方法を知っておくと良いでしょう。
今回は詳しく解説しませんでしたがuseStateや分割代入については過去に別の記事で解説しているので必要な方はご覧ください。
TypeScriptを使ったReactでのイベントと関数の書き方
続いてイベントと関数の書き方を紹介します。
基本的には引数と戻り値に型を指定するだけなので書き方さえ覚えればOKなことが多いです。
例題は少し変えてTODOリストを入力する画面を作っていきます。
import "./App.css";
// ここを変更
import Form from "./screens/ts-event/Form";
function App() {
return (
<>
{/* ここを変更 */}
<Form />
</>
);
}
export default App;
import React from "react";
const Form = () => {
return (
<div>
<form>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>検索</button>
</form>
<div>
<div>
<span>掃除</span>
<button onClick={() => deleteTodo(1)}>完了</button>
</div>
<div>
<span>料理</span>
<button onClick={() => deleteTodo(2)}>完了</button>
</div>
<div>
<span>買い物</span>
<button onClick={() => deleteTodo(3)}>完了</button>
</div>
</div>
</div>
);
};
export default Form;

このようなフォームにおいてinputタグとbuttonタグにそれぞれ関数を作っていきます。
・入力欄に入力したときの関数:handleChange
・検索ボタンをクリックしたときの関数:handleClick
・完了ボタンをクリックしたときの関数:deleteTodo
import React from "react";
const Form = () => {
// ここを追加
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
console.log("クリック");
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const deleteTodo = (id: number) => {
console.log(`${id}を削除`);
};
return (
<div>
<form>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>検索</button>
</form>
<div>
<div>
<span>掃除</span>
<button onClick={() => deleteTodo(1)}>完了</button>
</div>
<div>
<span>料理</span>
<button onClick={() => deleteTodo(2)}>完了</button>
</div>
<div>
<span>買い物</span>
<button onClick={() => deleteTodo(3)}>完了</button>
</div>
</div>
</div>
);
};
export default Form;
handleDeleteの引数は数字にしていますが、こちらは実際の仕様によりますので適した型を指定します。
初学者の方が慣れないのはeの型だと思います。
何やら難しい単語が書かれていますが「イベントの型<イベントが発生する要素の型>」という感じです。
イベントというのは「クリック」「入力」などのことで、これらのイベントの種類にはそれぞれ事前に型名が決められています。
さらにイベントが発生する場所、つまりHTML要素もinputタグやbuttonタグなどがあり事前に型名が決められています。
それぞれ膨大な種類があり事前に暗記することは現実的ではないので、学習や開発を通じて1個ずつ覚えるか調べるような形になるでしょう。
とはいえ頻出のものは何回も使っているうちに覚えられるので安心してください。
初学者の方は「eにも型を書かないといけない」とだけ覚えておきましょう。