React ContextでuseReducerを使ってみる

#はじめに
今回は**Context API**を用いた既存のプロジェクトにHooksの一つである**useReducer**を用いてreducerを追加していきます。ドキュメントを読んでもいまいち理解できない方向けの記事にしましたので細かい説明は省いています。そのままコピペしてみても良いかもしれません。

#useReducerとは何か
（ContextAPIを用いて作られたグローバルなstateに対して）**reducer**を設定できるReact Hooksの一つです。つまりContextでreducerを使えます。ただ、そもそもreducerとは何か。

#reducerとは何か
もともとは**Redux**において出てくる考え方で、**グローバルな**stateの状態を管理する場所です。**ローカルな**stateの更新はstateを設定したコンポーネント内で出来ますが（下の図参照）、reduxで作成するグローバルなstateはコンポーネント側が直接変更することは出来ません。各コンポーネントはreducerに対して、『この処理を行いたい』という申請を送ると、reducerが代わりにstateの値を更新します。

```react:App.js
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を追加します。
![react-app-files.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/749aa09d-be04-cdaa-66a1-3a9dee6a8348.jpeg)

とりあえずはHooksの**useState**を用いてローカルなstateを作成し、Providerにstateを渡しグローバルなstateを作成します。

```react:contexts/UserContext.js
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で囲んであげます。

```react:src/App.js
import React from 'react';
import UserContextProvider from './context/UserContext';
import UserList from './components/UserList';

const App = () => {
    return (
        <UserContextProvider>
            <UserList />
        </UserContextProvider>
    );
}
 
export default App;
```
stateの中身を簡単に表示させます。

```react:components/UserList.js
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
```
結果として、ブラウザ上ではこのように表示されます。（黒の枠線はありません。）
![Untitled Diagram (10).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/1c4e0331-abea-d122-171d-1ad37df6da03.jpeg)

### useReducerを追加後のプロジェクト
それではuseReducerを用いてContextにreducerを追加します。useReducerを使う前に、reducerを作ります。まずはsrcの下にreducersフォルダを作成し、その中に実際のreducerとなる**UserReducer.js**を作ります。dispatch関数を用いて送られてきたオブジェクトをもとにstateを操作します。返り値には更新後のstateを返しています。これだけでは掴みにくいと思うので下のコードで変更した部分を確認してみてください。

```react:src/reducers/UserReducer.js
// 下の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
    }
}
```

```react:contexts/UserContext.js
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;
```

```react:components/UserList.js
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
```
※App.jsの変更はありません。
![Untitled Diagram (10).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/d45100f9-4af9-df10-cfe6-e39791a7cb83.jpeg)

ご覧の通り**useReducer追加前**のものと同じ機能を持つアプリができました。これで完成です！

#最後に
ここまで読んでいただきありがとうございます。誤字、説明等の間違いがあった場合コメントください。