TypeScriptでZustand・Recoilの状態管理に型を適用する方法!初心者向け完全解説
生徒
「ReactやNext.jsでデータを管理するときに、ZustandやRecoilを使うと便利だと聞きました。でも、TypeScriptで型をつけるのが難しそうで不安です。」
先生
「確かに、状態管理に型を適用するのは最初は難しく感じるかもしれませんね。でも、TypeScriptを使うことで、どんなデータがどこにあるのかが明確になり、ミスを劇的に減らすことができるんですよ。」
生徒
「具体的に、どうやって型を定義して当てはめていけばいいのでしょうか?」
先生
「それぞれのライブラリに合わせた型定義のコツがあります。まずは基本から一緒に学んでいきましょう!」
1. 状態管理とTypeScriptの重要性
Webアプリ開発において、「状態管理」とはアプリケーションが保持しているデータ(ユーザー名、買い物カゴの中身、ログイン状態など)をどこに保存し、どう更新するかを制御することを指します。ReactやNext.jsでは、コンポーネント間でデータを共有するために、Zustand(ズスタント)やRecoil(リコイル)といったライブラリがよく使われます。
TypeScriptをこれらのライブラリと組み合わせて使う最大のメリットは、「データの形を保証できること」です。例えば、数字が入るはずの場所に文字を入れてしまったり、存在しないデータを参照しようとしたりしたときに、プログラムを実行する前にTypeScriptが「そこは間違っていますよ」と教えてくれます。これにより、初心者が陥りがちなバグを未然に防ぐことができます。
2. Zustandで型を適用する基本手順
Zustandは非常にシンプルで軽量な状態管理ライブラリです。TypeScriptで利用する場合、まずは「ストア(データを保存する場所)」にどのようなデータが入るのか、どのような関数で更新するのかを定義するインターフェース(Interface)を作成します。
インターフェースとは、いわば「データの設計図」のようなものです。パソコンを触ったことがない方でも、「この箱には名前(文字)と年齢(数字)を入れる」というルールをあらかじめ決めておくことだと考えれば分かりやすいでしょう。Zustandでは、この設計図をcreate関数のジェネリクス(型の引数)として渡すだけで、型安全なストアが完成します。
import { create } from 'zustand';
// ストアのデータの形を決める「設計図」を作ります
interface CounterState {
count: number; // 数字型のデータ
increase: (by: number) => void; // 数字を受け取って何も返さない関数
}
// 設計図(CounterState)を元にストアを作成します
const useCounterStore = create<CounterState>()((set) => ({
count: 0,
increase: (by) => set((state) => ({ count: state.count + by })),
}));
3. 複雑なオブジェクトをZustandで管理する
次に、単純な数字だけでなく、ユーザー情報のような複数の項目を持つデータを管理する方法を見ていきましょう。ここでは、ユーザーの名前やログイン状態を管理する例を考えます。TypeScriptでは、typeやinterfaceを使って、より複雑な構造を定義できます。
もし、データが「空(null)」になる可能性がある場合は、ユニオン型という仕組みを使います。これは「AまたはB」という状態を許容する書き方です。例えば、ログイン前はデータが空で、ログイン後はユーザー情報が入る、といった切り替えを安全に行うことができます。型を指定しておくことで、データが空のときに無理やり名前を表示しようとしてエラーになるのを防げます。
interface User {
id: string;
name: string;
}
interface UserState {
user: User | null; // ユーザーがいるか、空(null)の状態
login: (newUser: User) => void;
logout: () => void;
}
const useUserStore = create<UserState>()((set) => ({
user: null,
login: (newUser) => set({ user: newUser }),
logout: () => set({ user: null }),
}));
4. Recoilの基本とAtomへの型適用
RecoilはFacebook(現Meta)が開発したライブラリで、Atom(アトム)という単位でデータを管理します。Atomは、アプリケーションの中で共有できる最小のデータ単位です。TypeScriptでRecoilを使う場合、このAtomを作成する際に型を指定します。
具体的には、atom関数の引数にあるdefault値(初期値)に対して型を合わせる必要があります。型を指定しないと、TypeScriptは初期値から型を推論(予想)しますが、明示的に型を書くことで、後から予期せぬデータが入るのを防ぐことができます。初心者のうちは、面倒でもしっかりと型を記述する習慣をつけると、後々の学習がスムーズになります。
import { atom } from 'recoil';
// 文字列(string)だけを入れることができるAtomを定義します
export const textState = atom<string>({
key: 'textState', // アプリ内で一意(重ならない)名前
default: '', // 最初は空の文字を入れておきます
});
5. RecoilのSelectorで計算結果に型をつける
RecoilにはSelector(セレクター)という便利な機能があります。これは、Atomにあるデータを加工して、新しいデータを作り出す仕組みです。例えば、入力された文字の長さを数えたり、複数のAtomを組み合わせて合計金額を出したりする場合に使います。
Selectorにも型を適用することができます。Selectorが「何を返すのか」を定義しておくことで、その結果を使うコンポーネント側でも、データの種類を間違えることなく扱うことができます。プログラムにおける「戻り値」の型を意識することは、大規模な開発になればなるほど重要になってきます。
import { selector } from 'recoil';
// 文字の長さを計算するセレクター
export const charCountState = selector<number>({
key: 'charCountState',
get: ({get}) => {
const text = get(textState); // 上で作ったtextStateを取得
return text.length; // 文字の数を数字として返します
},
});
6. 型安全な状態管理がもたらす開発体験
TypeScriptを導入すると、コードを書いている最中にエディタ(VS Codeなど)が強力なサポートをしてくれるようになります。これを入力補完(オートコンプリート)と呼びます。例えば、ストアからデータを取り出そうとしたとき、どのようなプロパティが存在するのかを一覧で表示してくれます。
パソコンの操作に慣れていない方にとって、英単語を正確に入力するのは大変な作業です。しかし、TypeScriptがあれば、最初の数文字を打つだけで正しい候補を出してくれるため、タイピングミスによるエラーがほぼゼロになります。また、他の人が書いたプログラムを読むときも、型定義を見るだけで「これは何のためのデータか」がすぐに理解できるため、チーム開発でのコミュニケーションも円滑になります。
7. ZustandとRecoilのどちらを選ぶべきか
初心者の方が悩むポイントとして、「どちらのライブラリを使えばいいのか」という問題があります。結論から言うと、まずはZustandから始めるのがおすすめです。理由は、設定が非常に少なくて済み、TypeScriptとの相性も抜群に良いからです。Zustandは標準的なJavaScript/TypeScriptの書き方に近いため、基本を学ぶのに適しています。
一方で、Recoilは非常に高機能で、Reactの深い機能(Suspenseなど)と高度に連携できます。大規模で複雑なデータ構造を扱う場合はRecoilが力を発揮しますが、まずは小さなプロジェクトでZustandを使い、型定義の流れを掴むのが上達への近道です。どちらを使っても、TypeScriptによる型適用の考え方は共通していますので、片方をマスターすればもう片方の理解も早まります。
8. エラーが出たときの対処法とデバッグ
TypeScriptを使っていると、赤い波線(エラー)が出て困ることがあります。これは決して悪いことではありません。プログラムを動かす前に「間違い」を見つけてくれた幸運な瞬間です。エラーメッセージには、例えば「型 'string' を型 'number' に割り当てることはできません」といった内容が書かれています。
これは、「文字を入れるべき場所に数字を入れようとしていますよ」という意味です。このようなメッセージが出たときは、まず自分が定義した設計図(インターフェース)を見直し、次にデータを入れている場所を確認しましょう。型が一致するように修正するだけで、ほとんどの問題は解決します。このように、エラーを味方につけて学習を進めることが、プログラミング習得のコツです。
9. Next.jsでの状態管理の注意点
Next.jsでZustandやRecoilを使う場合、クライアントコンポーネントという概念を理解する必要があります。Next.jsはサーバー側でページを準備する仕組みがあるため、ブラウザだけで動く状態管理ライブラリを使うときは、ファイルの先頭に"use client"という記述が必要になります。
この記述を忘れると、TypeScriptが正しくてもエラーが発生してしまいます。型定義と合わせて、ライブラリが動作する環境(サーバーかクライアントか)を意識することも大切です。特にNext.jsの最新バージョン(App Router)を使う場合は、この切り分けが非常に重要になります。一歩ずつ、環境のルールと型のルールの両方に慣れていきましょう。