作成日時:2024年7月1日 02:48
更新日時:2024年7月1日 02:48
React
Next.js
(これは2023年11月に投稿した記事の再投稿です。諸事情により記事閲覧ができなくなっていたため修正しました)
HTNCodeです。
ReactにおけるGlobal State管理の方法について、useSWRを含めた方法をまとめておこうと思ったのでここに残します。
まずはpropsで渡す方法。
//page.tsx
"use client";
import State from "./State";
import { useState } from "react";
export interface Props {
value: string;
}
export default function Home() {
const [sampleState] = useState("この文字はpropsで渡しました");
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
<State value={sampleState} />
</main>
);
}
//State.tsx
import React from "react";
import { Props } from "./page";
const State = (props: Props) => {
return (
<div>
<h2>Propsで管理</h2>
{props.value}
</div>
);
};
export default State;
<結果>
きちんと渡されますが、階層が深くなるにつれ、いわゆるバケツリレーが発生します。
小規模な開発ならまだしも、中規模以上なら他の方法を検討した方がよいでしょう。
React標準の機能であるuseContextを使うパターン。
以下の手順で利用できます。
1.createContext()を使ってコンテキストを作成
// MyContext.tsx
import { createContext } from "react";
export const MyContext = createContext<string | null>("この文字は初期値です");
2.コンテキストプロバイダーに値を渡す
// page.tsx
"use client";
import State from "./State";
import { useState } from "react";
import { MyContext } from "./MyContext";
export default function Home() {
const [sampleText] = useState("この文字はuseContextで渡しました");
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
{/* コンテキストプロバイダーに値を渡す */}
<MyContext.Provider value={sampleText}>
<State />
</MyContext.Provider>
</main>
);
}
3.useContext()を使ってデータを取得
// State.tsx
import React, { useContext } from "react";
import { MyContext } from "./MyContext";
const State = () => {
const sampleText: string | null = useContext(MyContext);
return (
<div>
<h2>useContextで管理</h2>
<p>{sampleText}</p>
</div>
);
};
export default State;
<結果>
「この文字はuseContextで渡しました」と表示され、きちんと渡せていることが確認できます。
注意点として、コンテキストの箱に入っている値が複数の場合、そのうちどれか一つでも更新されると、
そのコンテキストが紐づく前コンポーネントが再レンダリングされます。
必要最低限の再レンダリングにするために、複数のコンテキストを作って管理するようにしましょう。
useContextのようにPropsで値を受け渡すことなくState管理する方法の一つです。
2015年から存在しているMeta社提唱のFluxアーキテクチャに則って設計されている状態管理のライブラリであり、
後述するRecoilの登場まではデファクトスタンダードに近いポジションを取っていたものです。
特に大規模なプロジェクトに適しているといわれています。
単一方向にしかデータが流れない、というところが特徴。
■Redux公式サイト
https://redux.js.org/
※参考図:公式サイトから引用
1.Store・・・アプリケーション全体の状態を保持する場所で、単一のオブジェクトです。これにより、状態を一元管理できます。
2.Action・・・アプリケーション内で何かが起こったことを示すオブジェクトです。アクションは状態の変更のトリガーとして機能し、必要な情報を含みます。
3.Reducer・・・アクションを受け取り、新しい状態を生成する関数です。リデューサーは純粋な関数であるため、同じ入力に対しては常に同じ出力を生成します。
4.Dispatch・・・ アクションを送信して、リデューサーによる状態の更新をトリガーします。これにより、アプリケーションの状態が変更されます。
Reduxを利用するには、reduxとreact-reduxをインストールします。
@reduxjs/toolkitには、Reduxのコアパッケージと、Reduxアプリの構築に不可欠な主要パッケージが含まれています。
react-reduxはreactのコンポ-ネントからreduxにアクセスするためのライブラリです。
npm install @reduxjs/toolkit react-redux
※単体でインストールするなら以下。
npm install redux react-redux
storeを作成。reducerは後ほど作る。
また、今回TypeScriptを使用しているため、RootState型を定義してエクスポートしておきます。
// MyStore.ts
import { configureStore } from "@reduxjs/toolkit";
import textReducer from "./reducers/textSlice";
const store = configureStore({
reducer: {
text: textReducer,
},
});
export default store;
// RootState型を定義してエクスポート
export type RootState = ReturnType<typeof store.getState>;
次に、状態をSliceに定義し、Reducerを作成します。
// textSlice.tsx
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
message: "この文字は初期値です",
};
const textSlice = createSlice({
name: "sampleText",
initialState,
reducers: {
setMessage: (state, action) => {
state.message = action.payload; // メッセージを設定するReducerを作成
},
},
});
export const { setMessage } = textSlice.actions; // 新しいReducerをエクスポート
export default textSlice.reducer;
※補足:
・State: 状態を保持するオブジェクトで、アプリの使用中に値は変化します。
・Action:更新の内容やタイプを指示するオブジェクトです。
・Reducer:Stateをアップデートする関数です。引数としてstateとactionを持ちます。
・Slice:Redux Toolkitに含まれる状態のデフォルト値やアクションを定義するオブジェクトです。
呼び出していきます。Providerタグでコンポーネントを囲むことでRedux Storeが使えるようになります。
// page.tsx
import State from "./State";
import { Provider } from "react-redux";
import store from "./MyStore";
export default function Home() {
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
{/* Providerタグでコンポーネントを囲むことでRedux Storeが使える */}
<Provider store={store}>
<State />
</Provider>
</main>
);
}
使いたいコンポーネント側で呼び出します。
// State.tsx
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { setMessage } from "./reducers/textSlice";
import { RootState } from "./MyStore"; //型をインポート
export default function State() {
const message = useSelector((state: RootState) => state.text.message);
const dispatch = useDispatch();
const handleClick = () => {
dispatch(setMessage("この文字はReducerを使って渡しました"));
};
return (
<div>
<h2>Stateコンポーネント</h2>
<p>メッセージ: {message}</p>
<button onClick={handleClick}>ボタンをクリック</button>
</div>
);
}
これでボタンをクリックすると「メッセージ:この文字はReducerを使って渡しました」と表示されるはずなのですが、
依存関係のエラーが出て使えなかったので、動作確認はできておりません。
万が一誤り等あれば、CONTACTページからご指摘いただけますと幸いです。
ちなみに、ここでは解説しませんが、今はredux-react の useSelector Hooksを利用することができるようです。
useSelectorに置き換えることでコードがスッキリします。
Meta社が2020年に公開した新しい状態管理のライブラリです。
Reduxのような学習コストがかかるものではなく、useStateと同じような感覚で使用することが可能です。
Reduxと同様にFluxというアーキテクチャに則って設計されており、Atom や Selector と呼ばれるものを使用して管理を行います。
まずはインストールします。
npm i recoil
続いて、atomを設定します。
//MyAtom.tsx
import { atom } from "recoil";
export const MyText = atom({
key: "MyText",
default: "この文字はRecoilを使って渡しました",
});
早速使えるようにしていきます。
// page.tsx
"use client";
import { RecoilRoot } from "recoil";
import State from "./State";
export default function Home() {
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
{/* RecoilRootタグでコンポーネントを囲むことで使えます */}
<RecoilRoot>
<State />
</RecoilRoot>
</main>
);
}
きちんと表示できました。
※2024/7/1追記:
layout.tsxは基本サーバーコンポーネントであるため、ページ丸ごとで状態管理したい場合に、layout.tsxでRecoilRootを使用したいケースがよく発生します。
そういった時は、以下のようにWrapperとなるコンポーネントを作成して、それをlayout.tsxでインポートして使用することで、クライアントコンポーネントとサーバーコンポーネントをきっちり分けることができます。
ご参考まで。
"use client";
import { RecoilRoot } from "recoil";
const RecoilRootWrapper = ({ children }: { children: React.ReactNode }) => {
return <RecoilRoot>{children}</RecoilRoot>;
};
export default RecoilRootWrapper;
useSWRは、Next.jsを開発しているVercelが開発した、Reactアプリケーションでデータの取得とキャッシュを管理するためのカスタムフックライブラリです。
useSWRは、データを非同期的にフェッチ、キャッシュし、必要に応じて再利用することができます。
これにより、APIリクエストやクエリの結果を簡単に管理し、アプリケーションのパフォーマンスを向上させることができます。
まずuseSWRフックの使い方から。
以下のようにuseSWRを使用することで、データのフェッチとキャッシュ、エラーハンドリング、ローディング表示などを簡単に管理できます。
import useSWR from 'swr';
function fetchData(url) {
const { data, error } = useSWR(url, fetch); // URLをキーとしてデータを取得
if (error) {
return <div>エラーが発生しました</div>;
}
if (!data) {
return <div>データを読み込んでいます...</div>;
}
return <div>データ: {data}</div>;
}
状態管理のためにuseSWRを使用したい場合、第2引数の fetcherを渡す箇所に対して、nullを渡します。
これにより、useSWRを使ったGlobal Stateの管理が行えるようになります。
const { data:sampleText, error:setSampleText } = useSWR(url, sampleText, null, { initialData: 'この文字は初期値です' });
useSWRはmutate関数を使用することでデータを更新できるので、これによってuseStateのような使い方ができるというわけです。
また、useSWRの第3引数には様々なoptionを指定できるので、initialDataの部分でデータの初期値を設定しておくことができます。
他にもReduxの代替え手段としてRecoil以外にも、useReducerを使う方法がありますし、
GraphQL APIをクライアント側で効率よく操作するためのライブラリApollo Clientを利用したState管理なんかもあります。
どのような開発環境でどの技術選定をするのか、状況に応じて使い分けをしていきましょう。
いつか振り返った時に役立つことを祈りつつ、まとめておこうまとめておこうと思いながら、
ずっと下書きにおいてものをやっと公開できました。笑
それではまた。
HTNCode