【React.js】useContextとuseReducerの使い方をご紹介(React hooksを学ぶ)
目次
今回は、前回に引き続きReact hooksについて記事をまとめていきます。
前回は、useStateとuseEffectの使い方を紹介しました。
【React.js】useStateとuseEffectの使い方をご紹介(React hooksを学ぶ)
https://www.dailyupblog.com/web_development/1888/
今回は、Context機能を利用できるフックであるuseContext、useState同様状態管理のフックであるuseReducerの2つのフックをご紹介します。
それでは早速いきましょう!!
useContextについて
useContextは、ReactのContext機能を利用できるフックです。
Context機能とは、Reactコンポーネントツリー上でデータを簡単にやり取りできるようにしてくれる機能です。
コンポーネント間でデータをやり取りする時はpropsを使いますが、コンポーネントの階層が複数になるとバケツリレーでデータを渡すことになるので、コードが複雑でわかりにくくなってしまいます。
その問題を解決すべく、コンポーネント間で簡単にデータアクセスできるようにしたのがContextです。
下図のように、Contextでは階層を飛ばして、データにアクセスすることが可能になります。
早速このuseContextでContext機能を使ってみましょう。
「components」フォルダ内に三つのコンポーネントファイルを作成します。
・Context1.tsx
import Context2 from './Context2';
const Context1 = () => <Context2 />
export default Context1
・Context2.tsx
import Context3 from './Context3';
const Context2 = () => <Context3 />
export default Context2
・Context3.tsx
import { useContext } from 'react';
import { UserDataContext } from '../App';
const Context3 = () => {
const userData = useContext(UserDataContext);
return (
<p>名前:{userData.name}<br />年齢:{userData.age}<br />職業:{userData.works}</p>
)
}
export default Context3
Context1.tsxではContext2.tsxを、Context2.tsxではContext3.tsxを呼び出して階層にしています。
そして、Context3.tsxでは、useContextを使用して、渡ってきたデータを出力しています。
今回は、App.tsxファイルのUserDataContextというユーザーのオブジェクトデータにアクセスして、そのデータを使用します。
さて、App.tsxも編集していきます。
「src」直下のApp.tsxの内容を下記のようにしてください。
import { createContext, useState } from 'react';
import './App.css';
import Context from './components/Context1';
type userData = {
name: string,
age: number,
works: string
}
export const UserDataContext = createContext<userData>({name: "",age: 0, works: ""})
function App() {
const [userData, setUserData] = useState<userData>({
name: "太郎",
age: 26,
works: "会社員"
})
return (
<div>
<UserDataContext.Provider value={userData}>
<Context />
</UserDataContext.Provider>
</div>
);
}
export default App;
ContextをはcreateContextで作成することができます。
createContextの構文は下記の通りです。
createContext(初期値)
今回は、typeScriptを使用しているので、userDataというtypeAriasを宣言しており、createContextにはgenericsでこのuserData型を指定しています。
このApp.tsxでは、Context1.tsxをインポートして呼び出していますが、createContextの現在値は、ツリー内でこのフックを呼んだコンポーネントの直近に存在する、Context.Providerのvalue属性の値によって決まります。
今回は、userDataをuseStateで作成しており、この値をvalueに入れています。
なので、このuserDataの値が変化することにより、Contextも変化するということです。
さて、今回の例を画面で実行してみましょう。
App.tsxを同階層のindex.tsxで読み込むようにしてください。
Reactを起動して、ブラウザで「http://localhost:3000」にアクセスしてみてください。
下記のようにユーザー情報が表示されていれば、OKです。
useReducerについて
useReducerは、useState同様、値を保持したり更新したりできる状態管理のフックです。
ここでは、useStateとはどのように違うのか、使い方の例を挙げて紹介していきます。
useReducterの大きな特徴としては、stateにactionというstateに関連する処理を加えてnewStateを作成することです。
このnewStateをreducerとして設定し、stateに更新を加える処理をactionで受け取った値によって出しわけて書くことが可能です。
と説明してもわかりにくいと思いますので、useReducerを使った例を見てみましょう。
ちなみに、useReducerの基本構文か下記になります。
const [state, dispatch] = useReducer(reducer, '初期値');
ここでの、「reducer」は、stateを更新するための関数になります。
また、「dispatch」は、reducerを実行するための関数になります。
変数宣言の際にstateの更新方法を先に宣言しておくことが可能です。
さて、簡単なカウンターを作ってみます。
「components」直下にCounter.tsxを作成し、その中に下記のように記述してください。
import { useReducer } from 'react';
type State = { count: number };
const initialState: State = { count: 0 };
type Action =
{
type: "INCREMENT",
} |
{
type: "DECREMENT",
} |
{
type: "RESET"
};
const reducer = (state: State, action: Action): State => {
switch(action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return {count: state.count - 1};
case 'RESET':
return {count: 0};
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>カウント:{state.count}</p>
<button onClick={() => dispatch({ type: "INCREMENT"})}>➕</button>
<button onClick={() => dispatch({ type: "DECREMENT"})}>➖</button>
</div>
)
}
export default Counter;
一つずつ見てみます。
まず下記でreducerの初期値を設定しておきます。
初期値である「initialState」は0であり、number型のcountプロパティをもつState型を宣言しており、この型を設定しています。
type State = { count: number };
const initialState: State = { count: 0 };
下記では、のちに設定するreducer関数のaction引数の型を宣言しています。
このaction引数はユニオンタイプによって、この3つどれかの値しか持ちません。
type Action =
{
type: "INCREMENT",
} |
{
type: "DECREMENT",
} |
{
type: "RESET"
};
下記がreducer関数です。
ここでは、のちにuseReducerを記述する際にあらかじめstateの更新内容を設定しておくことができます。
今回は、action引数の内容によって、stateに1を足すのか1を引くのか、はたまた0に戻すのかをswitch文で出しわけて書いています。
const reducer = (state: State, action: Action): State => {
switch(action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return {count: state.count - 1};
case 'RESET':
return {count: 0};
}
}
下記はCounterの関数コンポーネントになります。
この中でuseReducerを使用しています。
useReducerの第一引数には先ほど設定しておいたreducer関数を、第二引数には初期値としてinitialStateを入れています。
そして、htmlの中では、buttonタグにonClickでクリックイベントを設定していますが、そこにdispatchを入れており、引数に「INCREMENT」と「DECREMENT」をそれぞれ入れて、クリックした際に数値が足されるか引かれるかを記述しています。
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>カウント:{state.count}</p>
<button onClick={() => dispatch({ type: "INCREMENT"})}>➕</button>
<button onClick={() => dispatch({ type: "DECREMENT"})}>➖</button>
</div>
)
}
さて、ここまで書いたら、早速ブラウザで挙動を確認してみましょう。
index.tsxでは、今回作ったCounterコンポーネントが呼び出されるように書き換えてください。
ブラウザで「http://localhost:3000」にアクセスしたら、下記のように表示されます。
プラスボタンとマイナスボタンをクリックして、数値の表示が変化したらOKです。
useContextとuseReducerを合わせて使う
useContextとuseReducerは一緒に使うことが可能です。
先ほどのuseContextの例で、状態管理のuseStateを一緒に使っていましたが、useStateの代わりにuseReducerを使います。
カウンターの例を作ってみます。
先ほどと同じようにContext1.tsx、Context2.tsx、Context3.tsxの三つのコンポーネントを作成します。
Context1.tsxとContext2.tsxの内容は先ほどと同様なので省きます。
Context3.tsxは下記のように記述してください。
import { useContext } from 'react';
import { CountContext } from '../App';
const Context3 = () => {
const {state, dispatch} = useContext(CountContext);
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({type: "INCREMENT"})}>+</button>
<button onClick={() => dispatch({type: "DECREMENT"})}>−</button>
</div>
)
}
export default Context3
useContextでuseReducerのstateとdispatchを受け取ります。
そして、dispatch関数をそれぞれボタンのクリックイベントにセットします。
引数には、useReducerのreducer関数の第二引数であるactionの値を入れます。
プラスボタンには「{type: “INCREMENT”}」、マイナスボタンには「{type: “DECREMENT”}」を入れます。
続いて、App.tsxを下記のように書き換えてください。
import React, { useReducer } from 'react';
import './App.css';
import Context from './components/Context1';
type State = { count: number }
type Action = {
type: "INCREMENT"
} | {
type: "DECREMENT"
} | {
type: "RESET"
}
type ContextType = {
state: { count: number };
dispatch: (value: Action) => void;
}
export const CountContext = React.createContext<ContextType>({} as ContextType);
const initialState: State = { count: 0 }
const reducer = (state: State, action: Action): State => {
switch(action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return {count: state.count - 1};
case 'RESET':
return {count: 0};
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<CountContext.Provider value={{state, dispatch}}>
<Context />
</CountContext.Provider>
</div>
);
}
export default App;
まず、いくつかType Aliasを宣言しておきます。
「state」と「Action」はそれぞれuseReducerのreducer関数の引数で使う型になります。
他に「ContextType」という型を宣言していますが、これはcreateContextの引数で指定する型です。
createContextの引数に入れる初期値は、その後のContext.Providerのvalue属性の値と同一の型でなければなりません。
その後の流れは、reducer関数でstateの処理の出しわけを書き、useReducerのstateとdispatchをContext.Providerのvalue属性の値に設定しています。
これで、useReducerの値と関数をuseContextにて別コンポーネントで利用できるようになるわけです。
ブラウザで下記のように表示され、カウンターが機能すればOKです。
最後に
今回は、react hooksのuseContextとuseReducerの使い方をご紹介しました。
どちらもとても便利で、頻出も多そうです。
前回の2つ同様に使いこなせるようになりたいところです。
次回も同じようにreact hooksを紹介していくので、是非ご覧ください!
前回:【React.js】useStateとuseEffectの使い方をご紹介(React hooksを学ぶ)
https://www.dailyupblog.com/web_development/1888/
次回:【React.js】useCallback、useMemoの使い方をご紹介(React hooksを学ぶ)
https://www.dailyupblog.com/web_development/1956/