React入門

連載目次 : React入門
前回の記事 : useReducer / React Hooks

今回はReact Hooksの目玉機能の一つであるuseContextについて解説を行います。

useContextを利用することで様々なコンポーネントから参照できるステートを作成することができます。

useContextの基本

React.createContext()を利用することで、ステートの状態が可能なProviderコンポーネントが作成でき、value属性で管理したいステートを指定することができ、Provider内の子孫コンポーネントではuseContextを利用して管理しているステートにアクセスできます。

import React  from 'react';

export const MyContext = React.createContext();

export default function App() {
  return (
    <MyContext.Provider value="管理したい値">
      (Provider内)
    </MyContext.Provider>
  );
}

もう少し具体的なコードの落とし込んで挙動を確認していきましょう。

以下のサンプルではAppコンポーネント内でSiteContextという名前でコンテキストの定義をおこない、管理する値は{name: 'to-R Media'}というオブジェクトにします。

子孫コンポーネントとしてHeaderコンポーネントとBodyを読み込み配置しておきます。

import React  from 'react';
import Header  from './Header'
import Body   from './Body'

export const SiteContext = React.createContext();

export default function App() {
  return (
    <SiteContext.Provider value={{name: 'to-R Media'}}>
      <Header />
      <Body />
    </SiteContext.Provider>
  );
}

子孫コンポーネントではuseContextを利用して先祖要素にあるプロバイダーが持つ状態を取得できますのでsiteState.nameとしてコンポーネントに出力しています。

import React , {useContext} from 'react';
import { SiteContext } from './App';

export default function Header () {
  const siteState = useContext(SiteContext);
  return <h1>{siteState.name}</h1>
}
import React , {useContext} from 'react';
import { SiteContext } from './App';

export default function Body () {
  const siteState = useContext(SiteContext);
  return <p>これは「{siteState.name}」の内容です</p>
}

今回は親子関係のコンポーネントのためPropsで引き渡すほうが簡単なのですが、useContextを利用すれば深い位置に配置された子孫コンポーネントからも先祖のプロバイダーで管理している状態を取得できるので、コンポーネント構造が複雑にそして深くなる場合に力を発揮します。

useReducerとの連携

useContext単体ではただの状態の参照でグローバル変数等でも代替可能です。

useContextの真髄はuseReducerと組み合わせて更新可能なステートを作成できる所にあります。

先程のサンプルをuseReducerでラッピングしてnameを更新できるコンポーネントとしてFormを追加してみましょう。

src/App.jsにはuseReducerstatedipatchを取得できるコンポーネントしてSiteProviderコンポーネントを新たに作成しています。

import React , {useReducer} from 'react';
import Header  from './Header'
import Body   from './Body'
import Form   from './Form'

const initialState = {
  name : 'to-R Media'
}

function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE_NAME':
      return {
        ...state,
        name: action.payload
      };
    default : 
      return state
  }
}

export const SiteContext = React.createContext();

const SiteProvider = ({children}) => {
  const [state, dispatch] = useReducer(reducer, initialState)
  return <SiteContext.Provider value={{state, dispatch}}>
    {children}
  </SiteContext.Provider>
}

export default function App() {
  return (
    <SiteProvider>
      <Header />
      <Body />
      <Form />
    </SiteProvider>
  );
}

HeaderコンポーネントとBodyコンポーネントは機能自体は変わりませんが、取得時にstateから取り出す必要があります。

import React , {useContext} from 'react';
import { SiteContext } from './App';

export default function Header () {
  const { state } = useContext(SiteContext);
  return <h1>{state.name}</h1>
}
import React , {useContext} from 'react';
import { SiteContext } from './App';

export default function Body () {
  const {state} = useContext(SiteContext);
  return <p>これは「{state.name}」の内容です</p>
}

新たに作成したFormコンポーネントでは入力値に変更のタイミングでdispachを実行してSiteContextの値を更新するようにしています。

import React , {useContext} from 'react';
import { SiteContext } from './App';

export default function Form () {
  const { state, dispatch } = useContext(SiteContext);
  return <input
    type="text"
    value={state.name}
    onChange={e => dispatch({
      type:'CHANGE_NAME',
      payload:e.target.value
    })}
  />
}

これにより特定のコンポーネントで更新したステートが参照している全コンポーネントに反映されるようになりました。

Reduxのような状態管理ツールを利用している場合には、状態管理はReduxにまかせてしまったほうが良いですが、Reduxなどを利用していない場合にはuseContextを利用して各コンポーネント内で処理を完結できる仕組みを作ることができます。

次回はReact Hooksのもう一つの目玉機能のカスタムフックについて解説を行っていきます。

連載目次 : React入門
前回の記事 : useReducer / React Hooks