完成例 https://codesandbox.io/s/wloromw77

全体のまとめ

Context

  • Context は「状態」と「状態を変更するメソッド」を、アプリケーション全体で取り回せるようにさせるために用いる
  • Context.Provider を用いて値を渡す
  • Contect.Consumer を用いて値を受け取る
  • Hooks.useContext を用いれば、Contect.Consumer 相当の役割を果たすことができる

Hooks

  • 原理的には恐らく、Functional Component が持ち得ない、もしくは持ってはいけない、副作用を差し込む = Hook させるためにある
  • 主要なユースケースとしては、Class Component にしかなく、Functional Component にはなかった state, lifecycleMethod といった機能を Functional Component に持たせるために用いる(useState, useEffect)

Context + Hooks(useState, useContext)

  • これら三つを組み合わせることで、Functional Component だけで、Class Component が必須であった機能を実装できる

Context はなんのために使う?

Redux の役割の一つである「バケツリレーをせずに "state, state の変更メソッド" をコンポーネントで取り回す」ために使用する。

ただし、Redux ほど大規模な state を管理する機能はない。例えば action, action の dispatch といった機能はない。そのため、基本的には Redux が必要な規模感のアプリケーションにおいて context で代用することは難しい。

Class Component の state, setState もしくは Functional Component + useState の state, stateSetter を、Context.Provider を通して配下のコンポーネントに注入し、それを使用する。

Context の基本的な手法

Context を作る

Context を作る
export const BasicContext = React.createContext({ name: "default value" });

この Context を、各コンポーネントで共通して利用するので、外部で利用できるように export すること。

(与えた初期値の挙動は、直感的に期待する挙動と異なるので注意。後述する consumer が Provider を見つけることができないときにのみ参照される。つまり、consumer が Provider を見つけることができた場合には、たとえ Provider が値を提供していなくとも、この初期値は参照されない。)

Context.Provider コンポーネントを通して値を渡す

  • React Component の中で context.Provider コンポーネントを用いる
  • Context.Provider に対して、どんな props 名でもいいので名前を定義して値を渡す
  • この渡す値には、state, それから state を変更する setState などを含んだメソッドを渡す
  • 以下の例では Class Component を用いているので state, setState だが、Functional Component + useState でも OK(useState に関しては後述)
  • こうして渡した値は、このコンポーネント以下のコンポーネントにおいて、後述する Context.Consumer を用いて使用できる
context から Provider を作りこれを通して値を渡す
class App extends React.Component {
  state = {
    name: "Nakanishi"
  };

  changeState = () => this.setState({ name: "change" });

  render() {
    return (
      <BasicContext.Provider
        value={{ ...this.state, changeState: this.changeState }}
      >
        <div className="App">
          <ChildComponent />
        </div>
      </BasicContext.Provider>
    );
  }
}

Context.Consumer を用いて Context.Provider が渡した値を受け取る

  • (これは useContext を使用しない場合。useContext を使用する場合は後述する。)
  • 既に定義しておいた Context を import する
  • その Context から Consumer.Provider コンポーネントを作成し、
  • このコンポーネントでラップすると、Context.Provider が渡した値を受け取ることができる
Context.Consumer を用いる
import React from "react";
import { BasicContext } from "./index";
const ChildComponent = () => (
  <BasicContext.Consumer>
    {({ name, changeState }) => {
      console.log(name);
      return (
        <h2
          onClick={() => {
            changeState();
          }}
        >
          子供 {name}
        </h2>
      );
    }}
  </BasicContext.Consumer>
);

export default ChildComponent;

Context まとめ

  • Redux の役割の一つである、状態と状態変更のメソッドをコンポーネント内でバケツリレーをせずに取り回す機能を、Context は提供する。
  • React が元からもっている state, setState(もしくは後述する useState 関連の値)を用いて状態管理を行う。
  • React.createContext() を用いて Context を作成する。
  • Context.Provider を用いて配下のコンポーネントへ値を注入し、Context.Consumer を用いて配下のコンポーネントは値を受け取る。
  • Context.Provider を用いて配下のコンポーネントへ渡す値に、state, それから state を変更する setState を含んだメソッドを渡す。
  • Context.Consumer を用いて配下のコンポーネントは値を受け取る。
  • 値を受け取ったコンポーネントは state を UI にバインディングする。その state を変更するためには、同じく受け取った値である state 変更用のメソッドを使用する。

Hooks 入門: Hooks はなんのために使うのか?

まず大きな役割としては、Class Component にしかなく、Functional Component にはなかった state, lifecycleMethod といった機能を Functional Component に持たせるために用いることができる(useState, useEffect)

しかし原理的には恐らく、Functional Component が持ち得ない、もしくは持ってはいけない、副作用を差し込む = Hook させるためにある。

Hooks の基本的な手法 / useState

Hooks のうち useState は、Class Component の state, setState に当たる機能を提供する。

useState を使う
// useState を明示的に読み込む
import React, { useState } from "react";

const App = () => {
  // state の初期値 1 を渡す
  // 返り値は配列で、index 0 に state、index 1 に state を変更するメソッドが入っている
  const [state, setState] = useState(1);

  // useState の返り値の一つである state を変更するメソッドを用いる
  const plus = currentState => {
    setState(currentState + 1);
  };

  // useState の返り値の一つである state を UI にバインドする
  return (
    <div className="App">
      <h1>{state}</h1>
      <h2 onClick={() => plus(state)}>クリック</h2>
    </div>
  );
};

useEffect は Lifecycle Methods に相当する機能を提供する

userEffect は、Class Component の Lifecycle Methods に相当する機能を提供する。つまり以下:

  • componentDidMount
  • componentWillUnmount

ただし、useEffect という用語から正しく意味をとらえるのであれば、userEffect は、副作用、つまり「React Component に渡された props でも、React Component 内の状態でもないもの」を、参照、変更する = 副作用を処理するためにある。

例えば setInterval の使用や、外部の API の値への参照といったものがある。setInterval を使用する際に依拠することになる「時間」は、React Component に渡された props でもなく React Component の状態でもない、外部の値である。つまり副作用である。これを使用するために useEffect は有効である。また、外部 API からの値の取得も、同様に副作用であるため、これも useEffect を使用する。

useEffect の詳細については別記事でおこなう。

TotalProvider コンポーネントを作成し、Context で使用する値を全て渡す

複数の Context を作成した場合、各 Context ごとに Provider を作成し子要素をラップする作業は、非常に煩雑である。そのためこれを TotalProvider として切り出す。これは単なる自作コンポーネントであって、React Contect の機能ではない。このアイデアは 次の素晴らしい記事 から頂いた。ありがとうございます。

TotalProvider
import React, { useState } from 'react'


// 複数の Context を作成する
export const FilterContext = React.createContext('this is context!')
export const FilterProvider = FilterContext.Provider

export const InfoContext = React.createContext(true)
export const InfoProvider = InfoContext.Provider

// ラップされた子要素全てが、children に props に入ってくる
export const Provider = ({ children }) => {
  const filterInitialState = {
    error: true,
    warning: true,
    normal: false,
  }
  const infoStateInitial = false

  // この例では useState を使用している 
  const [filterState, setFilter] = useState(filterInitialState)
  const [infoState, setInfoState] = useState(infoStateInitial)
  
  // 
  return (  
    <InfoProvider value={{ infoState, setInfoState }}>
      <FilterProvider value={{ filterState, setFilter }}>
        {children} // 入ってきた子要素を使う
      </FilterProvider>
    </InfoProvider>
  )
}

TotalProvider の使い方

単に全体をラップする。

TotalProvider でラップする
import React from 'react'
import { Provider } from 'context/context'

const App = () => {
  return (
    <Provider>
      <SomeComponent />
    </Provider>
  )
}

export default App

React Context, Hooks(useState, useContext) の併用

上記 TotalProvider では、Class Component の state, setState ではなく、React Hooks の機能である useState を用いていた。さらに、React Hooks の useContext を使うことでコードを簡潔にすることができる。

useContext は、Content.Consumer に相当する機能を提供する。つまり、Context.Provider によって与えられた値を参照する機能である。

当然であるが、Context は React Component に Props によって与えられたものではなく、また React Component が内部で持つ状態でもないため、副作用である。そのため Hooks を用いる。

useContext
// useContext を明示的に読み込む
import React, { useContext } from 'react'

// 使用したい Context を読み込む
import { FilterContext } from 'context/context'

const Filter = () => {
  // useContext() に Context を渡す
  // Context.Provider で与えた値を受け取ることができる
  const { filterState, setFilter } = useContext(FilterContext)

  return (
    <div css={filterStyle}>
      <h2
        css={css`
          text-align: center;
          margin: 0;
        `}
      >
        Filter
      </h2>
    </div>
  )
}

export default Filter

全体のまとめ

Context

  • Context は「状態」と「状態を変更するメソッド」を、アプリケーション全体で取り回せるようにさせるために用いる
  • Context.Provider を用いて値を渡す
  • Contect.Consumer を用いて値を受け取る
  • Hooks.useContext を用いれば、Contect.Consumer 相当の役割を果たすことができる

Hooks

  • 原理的には恐らく、Functional Component が持ち得ない、もしくは持ってはいけない、副作用を差し込む = Hook させるためにある
  • 主要なユースケースとしては、Class Component にしかなく、Functional Component にはなかった state, lifecycleMethod といった機能を Functional Component に持たせるために用いる(useState, useEffect)

Context + Hooks(useState, useContext)

  • これら三つを組み合わせることで、Functional Component だけで、Class Component が必須であった機能を実装できる