ReactのContext APIを簡単実装!「constate」の使い方


「React Context API の状態管理が煩雑だ」

状態管理ソリューションの代替策に挙げられる React Context API。コンポーネント間の props のバケツリレーを回避できるソリューションであるが、たびたび引き起こされる煩雑さに悩まされる。

その要因は、コンテクスト( Provider )を展開するコンポーネントでローカルstate を定義するからではないだろうか。

多くのケースで、ルートに位置するコンポーネントに展開する。しかも複数存在することもある。こうなると該当コンポーネントで必要に応じてローカルstate を用意しなければならない。それに加えてコンテクストを外部ファイル化しているような場合、さらにややこしい。

ローカルstate とコンテクストの結びつきを目で追いかけなければならないのは保守性に欠けるうえに見通しが悪くなってしまう。可能ならば、ローカルstate とコンテクストの結びつきが明瞭かつプロジェクトのファイル構成に従いたい。

そのニーズにマッチしたライブラリがある。

このエントリーでは、Context API のラッパーライブラリ「constate」を紹介したい。

Context API とは?

Context APIは、任意階層のコンポーネント間でデータを共有する仕組みを提供するものだ。propsの明示指定を強制することなく、コンポーネントツリー間でデータを参照できる。これは、React v16.3で導入されたもので16.x以前のレガシーContext APIとは相容れない。将来のメジャーバージョンで削除される予定とのことだ。

https://reactjs.org/docs/context.html

constate について

Write local state using React Hooks and lift it up to React Context only when needed with minimum effort.

https://github.com/diegohaz/constate

導入

$ npm install constate --save
# or
$ yarn add constate

設置

import { useState } from 'react';
import constate from 'constate';

const [CountProvider, useCountContext] = constate(() => {
  const [count] = useState(0);
  return count;
});

使用例

かんたんな使用例として、モーダル実装を思い出してほしい。

モーダルを開くトリガー要素はいたる所に散らばっていて、それを梱包するコンポーネントとモーダルコンポーネントが兄弟関係にあたる構造は珍しくない。いかにも相性が良さそうだ。

/**
 * contexts/modal.js
 */
import { useState, useCallback } from 'react';
import constate from 'constate';

function useModal({ initialValue = false }) {
  const [modal, setModal] = useState(initialValue);
  const show = useCallback(() => setModal(true), []);
  const hide = useCallback(() => setModal(false), []);
  return { modal, show, hide };
}

export const [ModalProvider, useModalContext] = constate(useModal);
/**
 * components/Modal.js
 */
import React from 'react';
import styled from 'styled-components';
import { useModalContext } from '../contexts/modal';

const Wrapper = styled.div`
  overflow: hidden;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  opacity: ${props => props.modal ? 1 : 0};
  pointer-events: ${props => props.modal ? 'auto' : 'none'};
`

const Overlay = styled.div`
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, .2);
  cursor: pointer;
`

function Modal() {
  const { modal, hide } = useModalContext();

  return (
    <Wrapper modal={modal}>
      <Overlay onClick={hide} />
      ...
    </Wrapper>
  );
}

export default Modal;
/**
 * components/Main.js
 */
import React from 'react';
import { useModalContext } from '../contexts/modal';

function Main() {
  const { show } = useModalContext();

  return (
    <main>
      <button onClick={show}>モーダルを開くぜ</button>
    </main>
  );
}

export default Main;
/**
 * App.js
 */
import React from 'react';
import Modal from './components/Modal';
import Main from './components/Main';
import { ModalProvider } from './contexts/modal';

function App() {
  return (
    <ModalProvider>
      <Main /> 👈 トリガー要素を内包したコンポーネント
      <Modal /> 👈 モーダルコンポーネント
    </ModalProvider>
  );
}

export default App;

まとめ

React Context API のラッパーライブラリ「constate」の紹介だった。

コンテクストの煩雑さを防ぐ手段として、高階コンポーネント化することも効果的だろう。この場合においてもローカルstate に状態管理を任せてられるため、コンテクスト( Provider )を展開するコンポーネントの可読性は保たれる。

いずれの手段が優れているということはなく、React Hooks化するか、高階コンポーネント化するかの違いでしかない。単純に好みの問題になると思う。

このエントリーが、あなたのクリエイティビティを刺激するものであると期待したい。


Leave a Reply

Your email address will not be published. Required fields are marked *