【React.js】Hooks APIを使ったFunctional Component間のデータやイベントのやり取り


# はじめに

React初級者ぐらいの私が、Functional ComponentでHooks APIを使って以下をどう書くか知らべた結果を書きます。

* 親と孫でstateを共通管理する
* 親から孫の関数を呼び出す
* 子は、上記に関することを何も書かない
* React-Redux ってどうなる？

以下のHooks APIを使うことで実現できました。

* useState
* useContext
* useReducer

## 成果物

公式にもよく出てくる、Counterを+や-ボタンで増減させるアプリです。

アプリの動き
![sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1533/f0ee4db8-e16b-35ed-e4db-a4f774fc7657.gif "sample.gif")


[ここ](https://github.com/papettoTV/sample-react-hook-app/tree/fb88bce04911a79e5b958e9cc1810519dd137ee7)でコード参照できます。

## 背景

個々のhooksの機能や1ファイルでの書き方しか見つからず、親子孫がファイル分割された状態でどう書くの？というのが分からなかったので、調べながら実際に実装してみました。


# 解説

## ファイル構成


`public/components/`以下がメインで、このような構成になっています。

```
parent.js・・・親component
child.js・・・子component
grand_child.js・・・孫component
（一番知りたかったのは上記３つをどう書くか）
context.js・・・コンテキスト処理の共通化
reducer.js・・・reducer処理
```

この構成で、親と孫間で、共通のstate参照や、関数を呼び出したいときに
 `parent.js`, `grand_child.js` に何を書くのか、というのが知りたかったポイントです。

## コードの解説

### 親Component

まずは、関数（コールバック）やstateを共有したいcomponent達の親となるcomponent `parent.js`に、useReducerを書きます。

```javascript:parent.js
export default function ParentComponent() {
  const [state, dispatch] = useReducer(reducer, initialState)
```

※ `reducer` と `initialState` は[reducer.js](https://github.com/papettoTV/sample-react-hook-app/blob/fb88bce04911a79e5b958e9cc1810519dd137ee7/public/components/reducer.js)に切り出しました。

`state`や`dispatch`を親component配下のcomponent達で参照できる用contextを用意します。

```javascript:parent.js
import {ContextAppDispatch, ContextState} from "./context.js"
```

[context.js](https://github.com/papettoTV/sample-react-hook-app/blob/fb88bce04911a79e5b958e9cc1810519dd137ee7/public/components/context.js) は、孫でも参照するので外部化しました。

```javascript:context.js
import React from "react"

export const ContextAppDispatch = React.createContext("AppDispatch");
export const ContextState = React.createContext("state");
```

定義したものを、配下のcomponentが受け取れるようにします。

```javascript:parent.js
  return (
    <ContextAppDispatch.Provider value={dispatch}>
      <ContextState.Provider value={state}>
        <h1>ParentComponent</h1>
        <p>Count: {state.count}</p>
                （中略）
        <ChiledComponent />
      </ContextState.Provider>
    </ContextAppDispatch.Provider>
```

Contextが2重になっているのは後述します。

### 孫Componentで呼び出し

useContextで呼び出します。

```javascript:grand_child.js
import {useContext} from "react"
import {ContextAppDispatch,ContextState} from "./context.js"

export default function GrandChildComponent() {
  const dispatch = useContext(ContextAppDispatch);
  const state = useContext(ContextState);
  return (
    <>
      <h1>GrandChildComponent</h1>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
```

こうすることで、以下ができました。

* 親で定義したdispatchを孫も呼べる
* 親で定義したstateを孫も参照できる

このとき、子は何も上記に関与していません。

```javascript:child.js
import GrandChiledComponent from './grand_child.js'
export default function ChildComponent() {
  return (
    <>
      <h1>ChildComponent</h1>
      <GrandChiledComponent />
    </>
  )
}
```


## 複数のcomponent間でのデータやコールバックの受け渡し方法

今回の実装方針として、公式に書かれている方法にできるだけ沿うようにしました。

[公式](https://ja.reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down)に以下の記載があります.


> 大きなコンポーネントツリーにおいて我々がお勧めする代替手段は、useReducer で dispatch 関数を作って、それをコンテクスト経由で下の階層に渡す
>（中略）
> アプリケーションの state については、props として渡していくか（より明示的）、あるいはコンテクスト経由で渡すか（深い更新ではより便利）を選ぶ余地が依然あります。

ということで、（今回は大きなコンポーネントツリーではないですが）コールバックもstateも受け渡しはcontextでまとめることで、コードはすっきりしたと思います。

## 複数のcontextを使う

そうすると、

* dispatch用context
* state用context

の2つが必要になります。1つのcontextでも実装可能ですが、[公式](コンテクストの再レンダーを高速に保つために、React は各コンテクストのコンシューマをツリー内の別々のノードにする必要があります。)には

> コンテクストの再レンダーを高速に保つために、React は各コンテクストのコンシューマをツリー内の別々のノードにする必要があります。

とあるので、分けました。

それが以下の部分

```javascript:context.js
export const ContextAppDispatch = React.createContext("AppDispatch");
export const ContextState = React.createContext("state");
```


```javascript:parent.js
    <ContextAppDispatch.Provider value={dispatch}>
      <ContextState.Provider value={state}>
      （中略）
      </ContextState.Provider>
    </ContextAppDispatch.Provider>
```

## できたことまとめ

* 親、子、孫をFunctional Componentで実装できた
* 子に何も書かず、親と孫で共通のstateやactionを呼び出せた

# 疑問・課題

やり残したことや、新たに出てきた疑問など。React詳しい方のツッコミもらえると幸いです。

* contextは、 context.js を外部化して共通利用という方法で合ってる？
* reducer.js とか context.js の保存場所は componentsディレクトリとは別のディレクトリが良さそう
* action部分 `{type: 'increment'}` も外部化したほうが良さそう