useReducer
useStateと同様、状態管理に使用するもう一つのフック。
// useStateの場合
const [ state, setState ] = useState(0);
// useReducerの場合
const [ rState, dispatch ] = useReducer( prev => ++prev, 0);
・配列の0番目
状態管理(ステート)の変数。
・配列の1番目
更新用関数。useStateと違い、配列の1番目の更新用関数名はdispatch
と記載することが一般的。
(useStateのように任意の関数名でも動く。)
dispatchを実行すると、useReducerの引数で記載したprev => ++prev
が実行される。
ポイント
・useStateの場合
→ 更新用関数の中で、ステートをど更新するか定義する。
・useReducerの場合
→ useReducerの定義で、引数内でステートをどう更新するかを定義する。
useReducerの特徴
useStateと違い、複雑な処理を行なって更新するのに向いている。
import { useReducer, useState } from "react";
const Example = () => {
const [ state, setState ] = useState(0);
const [ rState, dispatch ] = useReducer(
(prev, action) => {
if(action === '+') {
return ++prev ;
}else {
return --prev;
}
}, 0);
return (
<>
<h3>{state}</h3>
<button onClick={() => setState(prev => prev + 1)}>+</button>
<h3>{rState}</h3>
<button onClick={() => dispatch('+') }>+</button>
<button onClick={() => dispatch('-') }>-</button>
</>
);
};
export default Example;
以下のように、関数を分けて定義し、useReducerの第一引数に渡してもOK.
import { useReducer } from "react";
const Example = () => {
const reducer = (prev, action) => {
if(action === '+') {
return ++prev ;
}else {
return --prev;
}
}
const [ rState, dispatch ] = useReducer(reducer, 0);
return (
<>
<h3>{state}</h3>
<button onClick={() => setState(prev => prev + 1)}>+</button>
<h3>{rState}</h3>
<button onClick={() => dispatch('+') }>+</button>
<button onClick={() => dispatch('-') }>-</button>
</>
);
};
export default Example;
reducer関数の定義
reducer関数
とは、dispatchによって実行される更新関数のこと。上記の例でいうredcuer
メソッドがreducer関数に該当する。
このreducer関数には、下記のように記述することが基本である。
// reducer関数の定義
// state → 現状のステート
// action → dispatchから受け取った、コンポーネントで実行された操作(データ)
const reducerFunction = (state, action) => {
// ステートを更新するための処理
return newState;
};
dispatchとは...?
dispatchとは、引数にactionという、コンポーネントによって実行された操作を受け取る。
→ actionというオブジェクトを引数に代入し、reducer関数に渡す。
第二引数はないので、複数渡すことはできない。
dispatch(action, value) // エラー
主に
{type: 捜査の種類, payload: 操作によって取得、また更新されるデータ}
のように、typeプロパティ('action'の識別子)とpayloadプロパティ(データ)で構成されることが多い。
基本的には、
①dispatchでreducer関数に対してactionを渡す。
②reducer関数はactionのtypeやpayloadの状況を利用してstateの更新を行う。
という形で利用するのが定石。
reducer関数とは...?
reducer関数は、stateとactionを引数に受け取り、stateを更新する関数のこと。
注意!!
reducer関数はstate
とaction
という2つの引数を受け取ることになっているが、
dispatchは必ずaction
という値(データ)のみを引数に取るので、redcuerが2つだからdispatchも2つという認識は誤り。
useContext
useContextは、アプリケーション全体(グローバル)でステートを管理したいときに使用する。
どういうときに使用するか
useContextがない場合、
// Example.jsx
import Child from "./components/Child";
const Example = () => {
const value = 'hello'
return <Child value={value}/>;
};
export default Example;
// Child.jsx
import GrandChild from "./GrandChild";
const Child = ({ value }) => (
<div style={{ border: "1px solid black", padding: 10 }}>
<h3>子コンポーネント</h3>
<GrandChild />
</div>
);
export default Child;
// GrandChild.jsx
const GrandChild = ({ value }) => {
return (
<div style={{ border: "1px solid black" }}>
<h3>孫コンポーネント</h3>
{value}
</div>
);
};
export default GrandChild;
Example.jsxから、子コンポーネントのGrandChild.jsxにpropsを渡したい場合、その間にあるChild.jsxに一度propsを渡すという工程が必要になる。
これをReactでは、Propsのバケツリレー
と呼び、propsの悪い渡し方の例となっている。
Childでは親のExample.jsxから渡されたpropsは使用しないため、不要なコードにもなる。
そのような場面で、ステートをグローバルに管理しておくことで、どの階層のコンポーネントにもpropsを一発で渡すことができる、useCotnextが便利である。
書き方
Example.jsx
import Child from "./components/Child";
// createContextをインポート
import { createContext } from "react";
// createContextでグローバルに管理するコンテキストを作成。
export const MyContext = createContext("helio");
const Example = () => {
const value = 'hello'
return <Child />;
};
export default Example;
GrandChild.jsx
// 親コンポーネントで作成したコンテキストをインポート
import { MyContext } from "../Example";
// useContextをインポート
import { useContext } from "react";
const GrandChild = () => {
// useContextを定義。このとき、引数にコンテキストを代入する。
const value = useContext(MyContext);
return (
<div style={{ border: "1px solid black" }}>
<h3>孫コンポーネント</h3>
{value}
</div>
);
};
export default GrandChild;
useContextとuseState
useContextを用いて、usaeStateをグローバルに管理できるようにする方法は以下の通り。
Example.jsx
import { createContext } from "react";
import { useState } from "react";
// コンテキストの新規作成
export const MyContext = createContext(0);
const Example = () => {
// ステートの定義
const [ value, setValue ] = useState(0);
return (
<>
// 作成したコンテキスト名.Provider というコンポーネントを作成。
// そのコンポーネントのpropsに、グローバル管理をしたい対象のステートを代入する。
<MyContext.Provider value={[ value, setValue ]}>
// コンテキストのコンポーネントタグに囲まれたコンポーネント内で、コンテキストの参照が可能になる。
<Child />
<OtherChild />
</MyContext.Provider>
</>
);
};
export default Example;
OtherChild.jsx
import { useContext } from "react";
import { MyContext } from "../Example";
const OtherChild = () => {
// 配列の2番目のみを取得したい場合、1番目を何も入力せず、[, ]とする。
const [, setValue ] = useContext(MyContext);
const clickHandler = (e) => {
setValue((prev) => prev + 1);
};
return (
<div style={{ border: "1px solid black" }}>
<h3>他の子コンポーネント</h3>
<button onClick={clickHandler}>+</button>
</div>
);
};
export default OtherChild;
Child.jsx
import GrandChild from "./GrandChild";
const Child = () => {
return (
<div style={{ border: "1px solid black", padding: 10 }}>
<h3>子コンポーネント</h3>
<GrandChild />
</div>
)
};
export default Child;
GrandChild.jsx
import { useContext } from "react";
import { MyContext } from "../Example";
const GrandChild = () => {
// GrandChildはExample.jsxのコンポーネントに囲まれていなかったが、
// 親コンポーネントであるChild.jsxがコンポーネントで囲まれていたので、GrandChildでも取得が可能となっている。
const [ value ] = useContext(MyContext);
return (
<div style={{ border: "1px solid black" }}>
<h3>孫コンポーネント</h3>
{value}
</div>
);
};
export default GrandChild;
useContextの注意点
useContextでは、ステートを更新した際、useContextを参照しているコンポーネントが全て再レンダリング
される。
これは、例え参照している値がステートの変数等ではなくても(更新用の関数などでも)、useContextを参照しているというだけで再レンダリングの対象になる。
参考文献