Chapter 09

Context を扱うコンポーネント:実装

Satoshi Takeda
Satoshi Takeda
2021.02.24に更新

Context を使った Hooks とコンポーネントの実装

ユーザーへのモーダルなメッセージを表示するケースや、任意のタイミングで任意の場所から固定位置のメッセージングが必要になるケースというのは往々にしてあります。

そういった場合表示するためのアクションを担うコンポーネント(A)と表示状態を扱うメッセージ自身を表示するコンポーネント(B)の作成が必要となり、どうしても(A)と(B)の距離が離れてしまうケースが多いでしょう。

こういった課題にグローバルな状態管理ライブラリを扱う場合もありますが、本章はサンプルにアラート表示のコンポーネントを取り上げ Context に状態(State)とアクション(Dispatch)の 2 つを用意しました。State Context を(A)となる Alert コンポーネントが扱い、Dispatch Context を(B)となる AlertForm コンポーネントが扱います。いずれの Context もカスタム Hooks として切り出されコンポーネント内部で利用していますが、このカスタム Hooks については追って解説します。

アラートメッセージを扱うコンポーネント 2 つ

下部に設置した AlertForm コンポーネントは Form を持っておりテキスト入力で実行されると、画面上部の Alert コンポーネントは入力されたテキストが表示します(初期状態は null なので何も表示されていません)。

Alert.tsx
export function Alert() {
  // State Context を扱うカスタム Hooks
  const { show, message } = useAlertState()
  if (!show) {
    return null
  }
  return <div style={styles}>{message}</div>
}
AlertForm.tsx
export function AlertForm() {
  const [text, setText] = useState("")
  // Dispatch Context を扱うカスタム Hooks
  const { showDispatcher, hideDispatcher } = useAlertDispatch()
  const onSubmitHandler = (e: FormEvent) => {/** 略 */}
  const onChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {/** 略 */}
  const onCloseHandler = () => {/** 略 */}
  return (
    <form onSubmit={onSubmitHandler}>
      <input type="text" value={text} onChange={onChangeHandler} />
      <button onClick={() => showDispatcher(text)}>Alert</button>
      <br />
      <button type="reset" onClick={onCloseHandler}>
        Alert Close
      </button>
    </form>
  )
}

State Context と Dispatch Context をカスタム Hooks から扱っています。どちらのコンポーネントも Context の実装詳細には関心がなく Context から得られる State と Dispatch(State を変更する関数)のみを知り、得られる値により UI を変更します。

Alert にいたっては値によって表示非表示とメッセージの適用をするのみですし、AlertForm も内部 State を持っていたりいくつかのイベントハンドラで Dispatch する関数を実行したりするのみでそこまで難しいコンポーネントではありません。

Context からカスタム Hooks を作成する

上記のコンポーネントで使用したカスタム Hooks によって得られる戻り値から、Context がどういった実装なのかを想像するのは難しくありません。

実装をすべて記載しません(かつ、ある程度省略し改変します)が、export されるカスタム Hooks と Context Provider を中心に解説します。まずは Context の作成とカスタム Hooks です。

AlertContext.tsx
// State Context の作成
const alertStateContext = createContext<AlertState>({ show: false, message: "" })
// Dispatch Context の作成
const alertDispatchContext = createContext<AlertDispatch>({
  showDispatcher: () => void 0,
  hideDispatcher: () => void 0
})
// State Context のためのカスタム Hooks
export const useAlertState = () => useContext(alertStateContext)
// Dispatch Context のためのカスタム Hooks
export const useAlertDispatch = () => useContext(alertDispatchContext)

ここで作成された Context は export されるカスタム Hooks 実行時の useContext の引数となります。Context を作成する createContext の初期値はここでは重要ではありません。以降で解説する Context Provider の初期値は別途指定のうえ value として下位コンポーネントに提供するからです。

次に作成した 2 つの Context を提供する Provider の実装を見てみましょう。

AlertContext.tsx
export function AlertProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, { show: false, message: "" })
  // 表示 Action を Dispatch させる関数を作成
  // Dispatch Context で提供される関数 showDispatcher となります
  const showDispatcher = (message: string) => dispatch({
    type: "表示アクション",
    payload: { message }
  })
  // 非表示 Action を Dispatch させる関数を作成
  // Dispatch Context で提供される関数 hideDispatcher となります
  const hideDispatcher = () => dispatch({ type: "非表示アクション" })

  // State Context, Dispatch Context の Provider それぞれでラップします
  return (
    <alertStateContext.Provider value={state}>
      <alertDispatchContext.Provider value={{ showDispatcher, hideDispatcher }}>
        {children}
      </alertDispatchContext.Provider>
    </alertStateContext.Provider>
  )
}

useReducer API の利用方法については公式ドキュメントを参考ください。アラートの初期状態(state)と特定のアクションを受けて状態を変更する関数(dispatch)を提供し、state は状態の Context へ、dispatch を使った表示非表示の Dispatcher は Dispatch Context の初期値に設定されています。

Context Provider のパターンとして、状態とアクションが分離しているパターンやそこそこ大きな State を Context として扱うパターンなどでは、関心の分離に合わせて Context を分割したほうが余計な再描画を抑止できるといったメリットがあります。パターンやプラクティスに関する解説が少なくないので調べて参考にしてもよいでしょう。

一方で取り出したい値以外の影響を受けずに目的の値を取り出せる、useContextSelector といった API を React 自身から提供することも RFC には上がっていますので頭の片隅にでも置いておいてください。

ここで実装された AlertProvider を利用するとラップしたコンポーネント内で先に実装している useAlertState Hooks がアラート表示の状態を、useAlertDispatch Hooks がアラート表示・非表示のための関数を提供します。


実装の解説が長くなってきたので分割し、次章でテストについて触れていきましょう。