ロードマップ
React 16.8 で追加された機能であるReactのHooks
について書いてあります。
書きながら学ぶ React Hooks 入門シリーズとして書き下ろしました。
これが最後です。
はじめに
Reactの組み込みフックであるとuseReducer
の説明をします。
useReducer とは
-
useState
と同じ様な状態管理用のhook- 基本的に、
useState
でできることはuseReducer
にもできます
- 基本的に、
-
state
とdispatch
(actionを送信する関数)を返す
構文
const [state, dispatch] = useReducer(reducer, initialState)
- reducer: state を 更新するための関数
- dispatch: reducer を 実行するための呼び出し関数
- dispath には action(何を実行するかをまとめたオブジェクト) を引数を渡す
実践1...カウントアプリ(state 1つ)
useState
でも実装できるカウントアプリを今回はuseReducer
を使って実装していきましょう。
手順
-
initialState
の定義 -
reducer
の定義 -
useReducer
の定義 - JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)
サンプルコード
import React, { useReducer } from "react";
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "reset":
return initialState;
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>{state}</p>
<button onClick={() => {dispatch('increment')}} >+</button>
<button onClick={() => {dispatch('decrement')}} >-</button>
<button onClick={() => {dispatch('reset')}} >reset</button>
</div>
);
};
export default App;
initialState
が0で、各reducer
がちゃんと機能してることがわかります。
実践2...カウントアプリ(複数のstate)
実践1を少し発展させた内容で、複数のstate
を持たせていきましょう。
usereducer
がuseState
より好ましいのは、複数の値を管理する複雑なステートロジックがある場合や、前のステートに基づいて、次のステートを決める必要がある場合です。
これを頭に入れておけば最適な使い分けの一助になるでしょう。
手順
- ①
initialState
の定義 - ② JSXにinitialStateの配置
- ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる
- ④
reducer
の定義- 今回はactionにtypeとvalueをセットしているので、Switch分のcaseの宣言は
action
→action.type
に変更 -
secondCounter
用にcaseを増やす - 複数のstateを含むので、spread構文を用いて
prevState
を展開し、更新します
- 今回はactionにtypeとvalueをセットしているので、Switch分のcaseの宣言は
- ⑤
useReducer
の定義(前と同じ) - JSXの記述(actionを表示するボタン、カウント数(state)を表示する部分)
サンプルコード
import React, { useReducer } from "react";
// ① `initialState`の定義
const initialState = {
firstCounter: 0,
secondCounter: 10
};
const reducer = (state, action) => {
// ④ actionにtypeとvalueをセットしているので、Switch分のcaseの宣言は`action`→`action.type`に変更
switch (action.type) {
case "increment1":
return { ...state, firstCounter: state.firstCounter + action.value }; // 複数のstateを含むので、spread構文を用いて`prevState`を展開し、更新します
case "decrement1":
return { ...state, firstCounter: state.firstCounter - action.value }; // 上に同じ
case "increment2":
return { ...state, secondCounter: state.secondCounter + action.value }; // 上に同じ
case "decrement2":
return { ...state, secondCounter: state.secondCounter - action.value }; // 上に同じ
case "reset":
return initialState;
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
{/* ② JSXにinitialStateの配置 */}
<p>カウント1: {state.firstCounter}</p>
<p>カウント2: {state.secondCounter}</p>
{/* ③ dispatch で渡している actionの中にペイロード(アクションの実行に必要な任意のデータ)を含ませる */}
{/* ③ action 内を Object にして、typeとvalueをセットする */}
<button onClick={() => {dispatch({ type: "increment1", value: 1 })}}>
+increment1
</button>
<button onClick={() => {dispatch({ type: "decrement1", value: 1 })}}>
-decrement1
</button>
<button onClick={() => {dispatch({ type: "increment2", value: 10 })}}>
+increment2
</button>
<button onClick={() => {dispatch({ type: "decrement2", value: 10 })}}>
-decrement2
</button>
<button onClick={() => {dispatch({ type: "reset" })}}>
reset
</button>
</div>
);
};
export default App;
実践3...useReducer × useContext
useReducer で(ローカルに)扱ってきたStateを useContext を使って (グローバルに)Stateを扱っていきたいと思います。
今回も前回の実践2と同様カウントアプリを作ります。(グローバルに)Stateにカウント値を持たせて各、子コンポネントで値の参照・更新ができるようにします。
手順
- 3つの子コンポネント A〜Cの作成
- 親コンポネントから子コンポネント3つをimport
-
reducer
とinitialState
の定義 -
useReducer
にreducer
とinitialState
を渡すことでcountStateとdispatch関数を作成 - createContextを使ってcountContextを作成(exportする)
- countContextを使って Provider を用意して valueには countとdispatchをセットする
- 各子コンポネントでは
- 渡されたcountContextをuseContextしてもちいる
- valueからcountを表示させとdispatch関数からincrement, decrementを使えるようにする
サンプルコード
import React, { useReducer, createContext, createElement } from "react";
import { ComponentA } from "./components/ComponentA";
import { ComponentB } from "./components/ComponentB";
import { ComponentC } from "./components/ComponentC";
export const CountContext = createContext();
const initialState = {
firstCounter: 0
};
const reducer = (state, action) => {
switch (action.type) {
case "increment1":
return { ...state, firstCounter: state.firstCounter + action.value };
case "decrement1":
return { ...state, firstCounter: state.firstCounter - action.value };
case "reset":
return initialState;
default:
return state;
}
};
const App = () => {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>カウント:{count.firstCounter}</h1>
<CountContext.Provider
value={{ countState: count, countDispatch: dispatch }}
>
<ComponentA />
<ComponentB />
<ComponentC />
</CountContext.Provider>
</div>
);
};
export default App;
import React, { useContext } from "react";
import { CountContext } from "../App";
export const ComponentA = () => {
const countContext = useContext(CountContext);
return (
<div>
<p>ComponentA</p>
<button
onClick={() => {
countContext.countDispatch({ type: "increment1", value: 1 });
}}
>
+increment1
</button>
<button
onClick={() => {
countContext.countDispatch({ type: "decrement1", value: 1 });
}}
>
-decrement1
</button>
<button
onClick={() => {
countContext.countDispatch({ type: "reset" });
}}
>
reset
</button>
</div>
);
};
import React, { useContext } from "react";
import { CountContext } from "../App";
export const ComponentB = () => {
const countContext = useContext(CountContext);
return (
<div>
<p>ComponentB</p>
<button
onClick={() => {
countContext.countDispatch({ type: "increment1", value: 1 });
}}
>
+increment1
</button>
<button
onClick={() => {
countContext.countDispatch({ type: "decrement1", value: 1 });
}}
>
-decrement1
</button>
<button
onClick={() => {
countContext.countDispatch({ type: "reset" });
}}
>
reset
</button>
</div>
);
};
import React, { useContext } from "react";
import { CountContext } from "../App";
export const ComponentC = () => {
const countContext = useContext(CountContext);
return (
<div>
<p>ComponentC</p>
<button
onClick={() => {
countContext.countDispatch({ type: "increment1", value: 1 });
}}
>
+increment1
</button>
<button
onClick={() => {
countContext.countDispatch({ type: "decrement1", value: 1 });
}}
>
-decrement1
</button>
<button
onClick={() => {
countContext.countDispatch({ type: "reset" });
}}
>
reset
</button>
</div>
);
};
実行結果
それぞれの子コンポネントの動作が確認できました。こうすることで、useReducer
とuseContext
を使ってグローバルにstateを管理することができます。
実践4...useReducer × 非同期処理(useEffect)
useReducer
とuseEffect
を使って非同期処理させて外部APIからデータ(記事の擬似データ)を取得・表示させてみましょう。
手順
-
useReducer
とuseEffect
のimport - 非同期処理は
axios
を使う -
initialState
の定義(loading, error, post) - reducerの作成
- 引数(
state
,action
) - return 新しいstate
- switch ケースは HTTPリクエストの成功時(
FETCH_SUCCESS
) or 失敗時(FETCH_ERROR
)で処理を分けます
- 引数(
-
useReducer
にinitialState
とreducer
関数を読み込ませて、state
とdispatch
関数を利用できるようにします -
useEffect
内にHTTPリクエスト非同期処理を記載する- 記事の擬似データを取得できるJSONPlaceholderからデータをとってくる(
GET
リクエストで) - 今回はこのURLにGETリクエストします
- 記事の擬似データを取得できるJSONPlaceholderからデータをとってくる(
- JSXに、
FETCH_SUCCESS
時とFETCH_ERROR
時の場合のコンポーネントを書く
サンプルコード
import React, { useEffect, useReducer } from "react";
import axios from "axios";
const initialState = {
loading: true,
error: "",
post: {}
};
const reducer = (state, action) => {
switch (action.type) {
case "FETCH_SUCCESS":
return {
loading: false,
error: "",
post: action.payload
};
case "FETCH_ERROR":
return {
loading: false,
error: "データ取得に失敗",
post: {}
};
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/posts/1")
.then((res) => {
dispatch({ type: "FETCH_SUCCESS", payload: res.data });
})
.catch(() => {
dispatch({ type: "FETCH_ERROR" });
});
}, []);
return (
<div>
<h1>{state.loading ? "Loading..." : state.post.title}</h1>
<p>{state.error ? state.error : null}</p>
</div>
);
};
export default App;
実行結果
非同期処理なので、時間差で 「Loading」がtitle
に変わっていることがわかります。
シーン別 useState と useReducer 使い分け
結論、シンプルなstateにはuseState
が向いていて、複雑なstate管理にはuseReducer
がいいでしょう。
表にして、特徴をまとめました。
useState | useReducer | |
---|---|---|
扱うといい state の型 | Number, String, Bool などのプリミティブ型を更新する時 | Array, Object の更新に使う時 |
どんなシーンで使うといい? | 単独のstate(複数でもできるっちゃできる) | 複数のstateを同時に更新したい時 |
stateの更新に複雑なビジネスロジックがある場合 | 好ましくない | 好ましい |
ストアーの種類 | ローカルな管理に向いてる | useContextと合わせてグローバルに管理するとよい |
以上です!これで最後です。
連載の最後に
シリーズ全部読んでくれた方へ。
どうもありがとうございました。
参考