LoginSignup
2
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-31

はじめに

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

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

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

  • useState
  • useContext
  • useReducer

成果物

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

アプリの動き
sample.gif

ここでコード参照できます。

背景

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

解説

ファイル構成

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

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

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

コードの解説

親Component

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

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

reducerinitialStatereducer.jsに切り出しました。

statedispatchを親component配下のcomponent達で参照できる用contextを用意します。

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

context.js は、孫でも参照するので外部化しました。

context.js
import React from "react"

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

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

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で呼び出します。

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を孫も参照できる

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

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

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

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

公式に以下の記載があります.

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

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

複数のcontextを使う

そうすると、

  • dispatch用context
  • state用context

の2つが必要になります。1つのcontextでも実装可能ですが、公式には

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

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

それが以下の部分

context.js
export const ContextAppDispatch = React.createContext("AppDispatch");
export const ContextState = React.createContext("state");
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'} も外部化したほうが良さそう
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3