TypeScriptの型定義をハンズオンで理解しよう
「Reactで開発を始めてみたものの、型定義で毎回つまずいてしまう…」そんな悩みをお持ちではないでしょうか?前回の記事で、TypeScriptでの基本的な型定義を学んだあなたは、もう一歩踏み込みたくなっているかもしれません。
本記事では、TypeScriptの型定義スキルをさらに強化するために、2つのシンプルなハンズオンに挑戦していきます。ReactコンポーネントにPropsやStateの型を付ける簡単な手順からはじめ、少しずつ理解を深めます。そして、ジェネリクスを用いて、より柔軟で再利用性の高い型定義を行う実践的なハンズオンにチャレンジします。
この記事を読み終える頃には、TypeScriptの基本的な記述方法を知り、型定義に関するコードの記述内容をより理解できるようになることを目的としています。 フロントエンド初心者の方がReactとTypeScriptでより自信を持って開発を進められるよう、本記事がそのサポートとなることを願っています。それでは、型定義の幅を広げるハンズオンを始めていきましょう。
この記事について
記事の目的
- TypeScriptの型定義の仕組みを理解し、使えるようになること
記事の対象者
- フロントエンドエンジニアを目指す初心者の方
- 静的言語の型について知りたい方
TypeScriptの関連記事について
TypeScriptの基本について詳しく学びたい場合は、以下の記事を参考にしてください。初心者向けのハンズオン形式で解説しています。
フロントエンジニアを目指す初心者向け TypeScriptの基礎を解説
https://envader.plus/article/340
この記事では基本的なTypeScriptの型定義方法についてふれていません。以下の記事で基本的な型定義方法について解説しています。
初心者向け!TypeScript型入門
https://envader.plus/article/515
React × TypeScript 基本の型定義
はじめに React × TypeScript での型定義の基本を学びましょう。Reactには独自の仕組み(JSX や Hooks など)があるため、TypeScriptの型システムと組み合わせる際に押さえておきたいポイントがあります。
関数コンポーネントに型をつける
まずは、ReactコンポーネントにPropsの型をつける方法から学びましょう。
// 型エイリアスを使用してGreetingPropsという型を作成
type GreetingProps = {
name: string; // 文字列
age: number; // 数値
};
ここでは型エイリアスを使って GreetingProps
という名前の型を作成しています。上記のようにtype GreetingProps = { ... };
と書くことで、独自の型を定義できます。この型は、必ずname
(文字列)と age
(数値)を持つというルールを定めています。
コンポーネントに型を適用する
続いて、定義した型をコンポーネントに適用します。
import React from "react";
// 型エイリアスを使用してGreetingPropsという型を作成
type GreetingProps = {
name: string; // 文字列
age: number; // 数値
};
// 関数コンポーネントにGreetingPropsを適用
function Greeting(props: GreetingProps) {
const { name, age } = props;
return (
<div>
<p>こんにちは、{name}さん!</p>
<p>あなたは{age}歳ですね。</p>
</div>
);
}
export default Greeting;
ここでコンポーネントに Greeting(props: GreetingProps)
を指定することで、コンポーネントが受け取る値 name
と age
に対して、型チェックが行われます。
型エラーの例
もしコンポーネントに間違った型の値を渡すと、TypeScriptがコンパイル時にエラーを表示してくれます。
import Greeting from "./Greeting";
// 正しい使い方
<Greeting name="Alice" age={25} />;
// 間違った使い方(エラー)
<Greeting name="Alice" age="25" />;
// エラー: ageはnumber型である必要があります
age
は 数値型であるべきですが、"25"
のように文字列を渡すとTypeScriptがエラーを出します。これにより、間違ったデータが渡るのを未然に防ぐことができます。
JSX.Elementで戻り値に型をつける方法
関数コンポーネントの戻り値に型をつける方法に、JSX.Elementがあります。 JSX.Elementは、Reactコンポーネントが返すJSX形式の要素を型として定義するための型です。これにより、戻り値が適切なReact要素であることをTypeScriptに伝えることができます。
以下は、JSX.Elementを使った型定義の例です。
import React from "react";
// Props(引数)の型を定義
type GreetingProps = {
name: string;
};
// JSX.Elementを使って戻り値の型を指定
function Greeting(props: GreetingProps): JSX.Element {
const { name } = props;
return <h1>こんにちは、{name}さん!</h1>;
}
上記のように : JSX.Element
を指定すると、「この関数は必ずReact要素を返す」ということを TypeScriptに伝えることができます。
例えば、以下のような戻り値はエラーになります。
// JSX.Elementで戻り値を型付け
function Greeting(props: GreetingProps): JSX.Element {
// 数値を返そうとする
return 123; // エラー: JSX.Element として返せません
}
Reactコンポーネントとしてレンダリングできないもの(数値や文字列など)を返すと、型エラーが発生するため、開発時点で問題に気づきやすくなります。
JSX.Elementの特徴
-
型安全性
関数コンポーネントが返す値がReact要素(JSX)であることを明確に示し、不適切な型の値を返さないようにします。
-
エラーの早期発見
戻り値の型が間違っている場合、コンパイルエラーが発生して問題を素早く修正できます。
-
シンプルな記述
必要最低限の型定義で、コードの可読性を保ちながら安全性を確保できます。
ここまでの学びを整理
ここまで、基本的なReact × TypeScriptでの型定義について学びました。
- Propsへの型付け
- 関数コンポーネントの戻り値を
JSX.Element
で指定
これらの基礎を押さえておくと、コンポーネント同士のデータ受け渡しが明確になり、開発時にバグを早期発見できます。
次のセクションから、ハンズオン形式で具体的な実装方法を学び、より実践的な知識を深めていきましょう。
ハンズオンで型定義をしてみよう
このセクションでは以下の2つの機能をハンズオンで作成します。
- 型定義付きのカウンター
- ジェネリクスで型定義するモーダル
このハンズオンをとおして実際にコードを記述し、React × TypeScript の型定義について、以下の理解を深めることを目的としています。
-
Propsの型定義
コンポーネント間のデータ受け渡しにおける型の重要性を理解し、適切な型定義の方法を学びます。
-
ステート管理と型定義
useState
を使ったステート管理に型を付けることで、状態の型を明確化し、適切に管理する方法を体験します。 -
型定義がもたらす安全性
型定義により、間違ったデータや値の操作を未然に防ぎ、コードの堅牢性と可読性を向上させる効果を確認します。
コードを手軽に試したい方には、以下のサイトがおすすめです。フロントエンド技術の環境構築が不要で、ブラウザ上でコードをすぐに試すことができます。
StackBlitz
StackBlitzについて、使用方法などは以下の記事で詳しく解説しています。
StackBlitzとは?初心者向け便利で簡単なツールを紹介!
https://envader.plus/article/434
1.型定義付きのカウンター作成
はじめに、型定義付きのカウンター作成の紹介です。React入門のコード例でよくある、ボタンを押すとカウント数が変化するカウンターにTypeScriptで型定義をします。
App.tsx
// App.tsx
import React, { useState } from "react";
// Buttonコンポーネントの Props 型定義
type ButtonProps = {
label: string; // ボタンのラベル
onClick: () => void; // クリックイベントハンドラ
};
// Buttonコンポーネントを function コンポーネントで定義
function Button(props: ButtonProps): JSX.Element {
const { label, onClick } = props;
return (
<button onClick={onClick} style={{ margin: "0 5px" }}>
{label}
</button>
);
}
// 親コンポーネントを function コンポーネントで定義
function App(): JSX.Element {
const [count, setCount] = useState<number>(0); // number 型を定義
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
return (
<div style={{ textAlign: "center" }}>
<h2>カウンター</h2>
<p>現在のカウント: {count}</p>
<div>
{/* Buttonコンポーネントを使用 */}
<Button label="-" onClick={decrement} />
<Button label="+" onClick={increment} />
</div>
</div>
);
}
export default App;
上記のコードを編集・実行すると、以下のようなシンプルなカウンターを作成できます。
このハンズオンでの型定義を詳しく見ていきましょう。
ButtonProps型を定義する
type ButtonProps = {
label: string; // ボタンに表示するテキスト
onClick: () => void; // クリックイベントハンドラ
};
この部分では、型エイリアス(type
)を使ってButtonコンポーネントが受け取るPropsの型を定義しています。
-
label: string;
文字列型のプロパティで、ボタンに表示されるテキストの型を指定しています。
-
onClick: () => void;
戻り値を持たない関数型のプロパティで、ボタンがクリックされたときに実行される関数を指定します。引数を取らず、何も返さない(
void
)ことを示します。
TypeScriptのvoid型とは?
void
型は「何も値を返さない」関数の戻り値を表す型です。React のイベントハンドラのように、実行されても何も値を返さない関数に対してよく使われます。
コンポーネントの型定義
function Button(props: ButtonProps): JSX.Element {
const { label, onClick } = props;
...
}
-
props: ButtonProps
この部分では、コンポーネントに渡されるPropsに
ButtonProps
の型を定義しています。例えば、このコンポーネントが必要とするlabel
(ボタンの表示テキスト)とonClick
(クリック時に動く関数)を、間違った型やデータで渡されることを防ぎ、安全に扱えるようにしています。 -
: JSX.Element
このコンポーネントが返す値がReactが描画可能なJSX(React要素)であることを表しています。これにより、数値や文字列など、Reactが描画できないデータを返してしまうミスを防ぐことができます。
ステート管理と型定義
const [count, setCount] = useState<number>(0);
この部分では、useStateフックに型を定義しています。
-
useState<number>
count
が必ず数値型であることをTypeScriptに伝えています。誤った型の値が代入される場合はコンパイルエラーとなり、型安全性が担保されます。 -
型推論について
型を明示せずに
useState(0)
と記述した場合でも、初期値が数値であるためTypeScriptが自動的に数値型を推論します。ただし、初期値が複数の型になり得る場合や明示的に型を指定したい場合には、useState<number>
のように型を指定することが推奨されます。
イベントハンドラの実装
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
ここでは明示的な型定義を行っていません。TypeScript の型推論機能により、useState<number>
から得られる count
が数値型であることが自動的に推論されます。
Propsの型定義を使ったボタンの活用
App
コンポーネントでは、Button
コンポーネントを再利用して以下のように呼び出しています。
<Button label="-" onClick={decrement} />
<Button label="+" onClick={increment} />
-
label
文字列以外の値を渡そうとするとエラーが発生するため、誤った型を使用してしまうリスクを下げられます。
-
onClick
関数以外(例:数値や文字列)を渡そうとするとエラーが発生するため、イベントハンドラとしての正しい実装を強制できます。
カウンター作成のハンズオンで学んだこと
このハンズオンでは、Props型定義や useStateフックの型指定など、React × TypeScript の基本的な型付けを体験しました。とてもシンプルなカウンターですが、以下のポイントを押さえられます。
-
コンポーネントの Props 型定義
ボタンなどの汎用コンポーネントを再利用しやすくなります。
-
ステート値の型定義
カウント値を安全に管理し、誤った代入を防ぐことが可能です。
-
イベントハンドラの型推論
TypeScriptの型推論で、過度な型定義を減らせます。
このように、型定義を通じてコードの安全性や可読性が向上することが実感できるでしょう。
2.ジェネリクスで型定義するモーダル
次のハンズオンは、ジェネリクスで型定義するモーダルを作成します。このハンズオンでは、ジェネリクスでの型定義や、ステート管理と型の結びつきを学ぶことができます。はじめにジェネリクスについて学んでおきましょう。
なぜジェネリクスを使うのか?
Reactコンポーネントは通常、Propsでデータを受け渡します。Propsが受け取るデータ型を厳密に管理できれば、ランタイムエラーを減らし、開発体験を向上させることができます。しかし、コンポーネントごとに表示するコンテンツや受け取るデータが状況によって変わる場合、汎用的な型定義が求められます。
そこで登場するのがジェネリクスです。ジェネリクスを用いれば、コンポーネントが扱うデータ型を呼び出し側で柔軟に指定できるため、再利用性と型安全性を両立しながら開発できます。
ジェネリクスとは?
ジェネリクスとは、型を引数として受け取る仕組みです。通常の関数が値を引数として受け取り、処理を柔軟にするのと同様に、ジェネリクスを使うことで「型」を引数として受け取り、柔軟に適用できます。
通常の型定義は以下のように、あらかじめ型を固定してしまいます。
// 通常の型定義 id:数値とname:文字列の型を持つというルールに固定される
interface User {
id: number;
name: string;
}
一方でジェネリクスを使うと、型パラメータ(<T>
など)を定義し、それを必要な場所へ適用できるようになります。下記の例では、Response<T>
という型パラメータを定義し、T
が指し示す実際の型を呼び出し元で指定します。
interface Response<T> { // 型パラメーターを定義
data: T; // コンポーネントから型を引数として受け取る
}
// 使用例:data部分にUser型を適用
const userResponse: Response<User> = {
data: { id: 1, name: "Alice" },
};
これにより、Response<User>
や Response<number>
, Response<string[]>
など、さまざまな状況で使い回せる、汎用的な型定義が可能になります。
ジェネリクスを使用する効果
このハンズオンでは、モーダルが表示する「コンテンツデータ」の型をジェネリクスで渡せるようにします。
モーダルは「ユーザー情報」を表示し、別のモーダルは「商品詳細」を表示させます。その場合、一つのModalコンポーネントを汎用的に定義して、表示するデータの型をモーダル使用時に指定できると便利です。ジェネリクスを使用すれば、それが簡単に実現できます。
ジェネリクス適用のモーダル作成
それでは、モーダルコンポーネントにジェネリクスを適用してみましょう。以下の2つのファイルを作成し編集します。
- App.tsx
- Modal.tsx
App.tsx
// App.tsx
import { useState } from 'react';
import Modal from './Modal';
// ユーザー情報の型
interface User {
id: number;
name: string;
email: string;
}
// 商品情報の型
interface Product {
id: number;
name: string;
price: number;
}
export default function App() {
const [isUserModalOpen, setIsUserModalOpen] = useState(false);
const [isProductModalOpen, setIsProductModalOpen] = useState(false);
// ユーザー情報
const userData: User = { id: 1, name: 'Alice', email: 'alice@example.com' };
// 商品情報
const productData: Product = { id: 101, name: 'PC', price: 150000 };
return (
<div style={{ padding: '20px' }}>
<h2>ジェネリクスとモーダル</h2>
<p>1.ユーザー情報モーダル</p>
<button onClick={() => setIsUserModalOpen(true)}>
ユーザーモーダルを開く
</button>
{/* User型をモーダルへ */}
<Modal
open={isUserModalOpen}
onClose={() => setIsUserModalOpen(false)}
data={userData}
/>
<p style={{ marginTop: '20px' }}>2.商品情報を確認する</p>
<button onClick={() => setIsProductModalOpen(true)}>
商品モーダルを開く
</button>
{/* Product型をモーダルへ */}
<Modal
open={isProductModalOpen}
onClose={() => setIsProductModalOpen(false)}
data={productData}
/>
</div>
);
}
Modal.tsx
// Modal.tsx
import React from 'react';
interface ModalProps<T> {
open: boolean;
onClose: () => void;
data: T; // データの型はジェネリクスで受け取る
}
export default function Modal<T>(props: ModalProps<T>) {
const { open, onClose, data } = props;
if (!open) return null; // openがfalseなら何も表示しない
return (
<div style={overlayStyle}>
<div style={modalStyle}>
<h3>Modal Content</h3>
{/* dataオブジェクトの内容を展開して表示 */}
{Object.entries(data).map(([key, value]) => (
<p key={key}>
<strong>{key}:</strong> {value}
</p>
))}
<button onClick={onClose}>閉じる</button>
</div>
</div>
);
}
// 簡易的なスタイル例(詳細は割愛)
const overlayStyle: React.CSSProperties = {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
};
const modalStyle: React.CSSProperties = {
backgroundColor: '#fff',
padding: '20px',
minWidth: '200px',
maxWidth: '400px',
borderRadius: '8px',
boxShadow: '0 0 10px rgba(0,0,0,0.3)',
};
コードを編集すると以下のモーダル機能が作成できます。
このコードを動かすと、「ユーザーモーダル」ボタンと「商品モーダル」ボタンをクリックすることで、異なる型のデータ(ユーザー情報/商品情報)を同じモーダルコンポーネントで扱うことができます。モーダル内では、Object.entries(data)
を使ってデータを表示しています。
通常、モーダルに表示する内容は状況によって変わるため、型を一つずつ書き換えると煩雑になることがあります。しかしジェネリクスを使えば、型パラメータ(<T>
)で柔軟に型を指定でき、モーダルコンポーネントの再利用性が高まります。
モーダル作成ハンズオンで学んだこと
ジェネリクスを活用することで、コンポーネントをより汎用的に再利用でき、開発規模が大きくなっても型安全性を維持しやすくなります。
今回のハンズオンでは、シンプルな例としてモーダルを作成しました。次のステップとして、制約付きジェネリクス(<T extends { ... }>
など)や、ジェネリクスを使った複雑な型推論など、さらにTypeScriptならではの強力な型システムに触れてみるのがおすすめです。
これらを理解していくことで、Reactコンポーネントの設計範囲がさらに広がり、大規模アプリケーション開発にも役立つ知識が身につきます。
この記事のまとめ
ここまでTypeScriptの型定義について学んできました。最後に大切なポイントを振り返りましょう。
React × TypeScript での型定義の基本について、Reactには独自の仕組みとTypeScriptの型システムを組み合わせる以下の方法を学びました。
- 型エイリアスで定義した型をコンポーネントへ適用
- JSX.Elementで関数コンポーネントの戻り値に型をつける方法
ハンズオンでは2つの機能を作成し、型定義を通じてコードの安全性や可読性が向上することを実感しました。
-
コンポーネントのPropsとステート値の型定義
Propsやステートに型を付けることで、コードの可読性と保守性が向上することを実感しました。
-
ジェネリクスについて、メリットとその使用方法
汎用性の高い型定義を行うことで、再利用可能で型安全性を担保したコンポーネント設計が可能になることを学びました。
難しく感じていたTypeScriptの型定義が、今回の記事の内容を基礎として活用することで、初心者の方の理解を深めスキルアップのきっかけになれたら幸いです。
これからさらに多くのコードを書き、さまざまな型付けのパターンに触れてみてください。小さなコンポーネントから始めて、少しずつ大規模な設計に挑戦しましょう。
「やってみることは自信につながる」ことを意識し、次の学びへの一歩を楽しんでください。
参考資料
以下のリンクは、この記事で説明した手順や概念に関連する参考資料です。より詳しく学びたい方は、ぜひご覧ください。
-
サバイバルTypeScript
-
React TypeScript Cheatsheets
-
React公式
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2024.12.17
初心者向け!TypeScript型入門
この記事では、TypeScript初心者が理解しやすいように「型定義」の基本をやさしく解説します。
- TypeScript
2025.01.08
初心者向け!React × TypeScriptでジェネリクスを理解しよう
この記事では、ジェネリクスの基本をやさしく解説し、ReactとTypeScriptを組み合わせた実践例を通じて、具体的な使い方を解説しています。
- React
- TypeScript
2025.01.08
初心者向け!React × TypeScriptで作る天気予報アプリハンズオン
この記事では、ReactとTypeScriptを使って、天気予報アプリを一緒に作りながら、API通信やRESTの基本を学びます。
- TypeScript
- React