#はじめに
今回はContext APIを用いた既存のプロジェクトにHooksの一つであるuseReducerを用いてreducerを追加していきます。ドキュメントを読んでもいまいち理解できない方向けの記事にしましたので細かい説明は省いています。そのままコピペしてみても良いかもしれません。
#useReducerとは何か
(ContextAPIを用いて作られたグローバルなstateに対して)reducerを設定できるReact Hooksの一つです。つまりContextでreducerを使えます。ただ、そもそもreducerとは何か。
#reducerとは何か
もともとはReduxにおいて出てくる考え方で、グローバルなstateの状態を管理する場所です。ローカルなstateの更新はstateを設定したコンポーネント内で出来ますが(下の図参照)、reduxで作成するグローバルなstateはコンポーネント側が直接変更することは出来ません。各コンポーネントはreducerに対して、『この処理を行いたい』という申請を送ると、reducerが代わりにstateの値を更新します。
import React, { useState } from 'react';
const App () => {
const = [data, setData] = useState('before');
const handleClick = () => {
setData('after');
};
return (
<div className="app">
<div>{ data }</div>
<button onClick={handleClick}>Change state</button>
</div>
)
};
export default App;
#useReducerの使い所
ContextAPIを用いて作られたグローバルなstateの状態を管理する場所としてreducerを設定します。reduxと違いContextはreducerを必要といないため、Context内でstateを更新する関数を定義しても良いのですが、大きなアプリケーションを作る際はstateとそれを更新する関数をreducerを使って分けた方が見やすいコードになり、何よりContextのコード量を減らせます。それでは実際にContextでreducerを設定しましょう。
#Context × Reducer
useReducerを追加前のプロジェクト
まずはグローバルなstateであるContextを持つ簡単なReactプロジェクトを作成します。ファイル構成、コードは以下の通りです。以下のプロジェクトにuseReducerを用いてContextにreducerを追加します。
とりあえずはHooksのuseStateを用いてローカルなstateを作成し、Providerにstateを渡しグローバルなstateを作成します。
import React, { createContext, useState } from 'react';
const initialUsers = [
{ name: 'Taro', age: 20, id: 1 },
{ name: 'Kai', age: 18, id: 2 },
{ name: 'Ryo', age: 23, id: 3 }
]
export const UserContext = createContext();
const UserContextProvider = ({children}) => {
const [users, setUsers] = useState(initialUsers);
// もちろん下のようにstateの状態を管理する関数もreducerなしで作れます。
const deleteUser = (id) => {
const newUsers = users.filter(user => user.id !== id)
setUser([
...newUsers
])
}
return (
<UserContext.Provider value={{users, deleteUser}}>
{children}
</UserContext.Provider>
)
}
export default UserContextProvider;
子コンポーネント(UserList)をProviderで囲んであげます。
import React from 'react';
import UserContextProvider from './context/UserContext';
import UserList from './components/UserList';
const App = () => {
return (
<UserContextProvider>
<UserList />
</UserContextProvider>
);
}
export default App;
stateの中身を簡単に表示させます。
import React, { useContext, Fragment } from 'react';
import { UserContext } from '../context/UserContext';
const UserList = () => {
const { users, deleteUser } = useContext(UserContext);
const userList = users.map(user => {
return (
<section key={user.id}>
<h3>{ user.name }</h3>
<div>年齢:{ user.age }歳</div>
<button
type="button"
onClick={() => deleteUser(user.id)}
>ユーザーを消去</button>
</section>
)
})
return (
<Fragment>
<h2>ユーザーリスト</h2>
{userList}
</Fragment>
);
}
export default UserList
結果として、ブラウザ上ではこのように表示されます。(黒の枠線はありません。)
useReducerを追加後のプロジェクト
それではuseReducerを用いてContextにreducerを追加します。useReducerを使う前に、reducerを作ります。まずはsrcの下にreducersフォルダを作成し、その中に実際のreducerとなるUserReducer.jsを作ります。dispatch関数を用いて送られてきたオブジェクトをもとにstateを操作します。返り値には更新後のstateを返しています。これだけでは掴みにくいと思うので下のコードで変更した部分を確認してみてください。
// 下のuserReducerの引数actionは
// dispatch関数を用いて送られてきたオブジェクトが入ります
export const userReducer = (state, action) => {
switch (action.type) {
case 'DELETE_USER':
// 今回はありませんがusersを含む全てのstateを返し
// その後usersの中身を更新しています
return {
...state,
users: [
...action.payload
]
}
default:
return state
}
}
import React, { createContext, useReducer } from 'react';
import { userReducer } from '../reducers/UserReducer';
// 変更部分。分かりやすくするためにオブジェクトとします。
const initialState = {
users: [
{ name: 'Taro', age: 20, id: 1 },
{ name: 'Kai', age: 18, id: 2 },
{ name: 'Ryo', age: 23, id: 3 }
]
}
export const UserContext = createContext();
const UserContextProvider = ({children}) => {
// 変更部分。useReducerに使いたいreducer、stateの初期値を入れます。
// useStateと似ていてstateとdispatch関数を返します。
const [state, dispatch] = useReducer(userReducer, initialState);
// 変更部分。stateとdispatch関数をグローバルで使えるようにします。
return (
<UserContext.Provider value={{state, dispatch}}>
{children}
</UserContext.Provider>
)
}
export default UserContextProvider;
import React, { useContext, Fragment } from 'react';
import { UserContext } from '../context/UserContext';
const UserList = () => {
// 変更部分。
const { state: { users }, dispatch } = useContext(UserContext);
// 変更部分。引数のidを用いて、そのidのユーザーを除いたnewUsersを作成。
// dispatch関数を呼び出し、typeを指定し、ペイロードとしてnewUsersを入れる。
const deleteUser = (id) => {
const newUsers = users.filter(user => user.id !== id);
dispatch({
type: 'DELETE_USER',
payload: newUsers
})
}
const userList = users.map(user => {
return (
<section key={user.id}>
<h3>{ user.name }</h3>
<div>年齢:{ user.age }歳</div>
<button
type="button"
onClick={() => deleteUser(user.id)}
>ユーザーを消去</button>
</section>
)
})
return (
<Fragment>
<h2>ユーザーリスト</h2>
{userList}
</Fragment>
);
}
export default UserList
ご覧の通りuseReducer追加前のものと同じ機能を持つアプリができました。これで完成です!
#最後に
ここまで読んでいただきありがとうございます。誤字、説明等の間違いがあった場合コメントください。