23個のサンプルで一貫したこと


Hooks に限った話ではないですが、React では最適化をサボるとすぐに不要な再計算や rerender が発生します。GUI設計の話に入る前に、本日は memoize と useContext による最適化について考えます。

memoize が肝心

scroll や touchMove などを利用したGUIの場合は特に気をつけないといけません。下手をすると指が画面に触れている間、包含コンポーネントで rerender しっぱなしという事が起こり得ます。この課題への取り組みとして3つの方法があります。いずれも共通点として、コンポーネントを細分化し rerender 層を薄くする事が重要です。

  • props.children による回避
  • renderProps + useMemo による回避
  • ContextAPI + useMemo による回避

ContextAPI を利用することで Dan が言う complex pattern 解消に近づくので、サンプルでは積極的に ContextAPI を利用しています。

Container 相当の Memoized Component

memoize input array は初見煩わしく思われるかもしれませんし、「将来的にコンパイラが自動で生成してくれる様になる」という一文が公式に注記されています。一方で、この memoize input array が意図的な rerender コントロールを有効にしてくれます。サンプルでよく利用した雛形が以下の様なものです。

  • 「View」 は Hooks さえ許容しない Stateless Function
  • そのひとつ高階層「Container」で memoize し props 注入
  • 「View」の rerender を Container memoize input array でコントロール

Hooks API を利用出来る層を限定する切り口は「従来SFCは従来のままで良い」ので個人的に推しです。

Container を構えると、ここで将来 useRedux や useApollo などが合流可能に見えますね。

// ____________________________________
//
// @ View

const View = (props: Props) => (...)
// ____________________________________
//
// @ Container

export default () => {
  const { ... } = useContext(...)
  return useMemo(
    () => (
      <View {...} />
    ),
    [...] // here!!
  )
}

Context Hooks への集約

However, we often can’t break complex components down any further because the logic is stateful and can’t be extracted to a function or another component.

Hooks 利用モチベーションで強調されている一文。Custom Hooks はポータブルで単一責務にするのが良さそうです。

ただし GUI を作るとなると、それらを複合した中粒度の Custom Hooks が欲しくなります。サンプルの中では以下の様に、Custom Hooks 戻り値と Context 定義を対にするシーンが何度もあります。この Context と対の Custom Hooks にビジネスロジックを集約、状態や更新ハンドラを限定的に末端コンポーネントへ公開しています。

import { createContext } from 'react'
import { usePieChart } from './usePieChart'

export const PieChartContext = createContext(
  {} as ReturnType<typeof usePieChart>
)

Custom Hooks の戻り値は自由なので、状態の公開・非公開など、スコープを閉じやすいです。また型との相性も良く、上記の定義だけ初期指定しておけば、実装に推論が追従してくれます。

useReducer を使っていない理由

そもそもこの中粒度で reducer は必要ないという点がひとつ。useReducer は、いずれ来るであろう useRedux が混在した時、混乱の元になると考えているためです。

筆者は Hooks を利用しても、Redux は引き続き使い続けたいと思っています。ContextProvider を中粒度コンポーネントの状態管理 gateway として Store に接続するのが良さそう、と考えています。Redux を「Hooks と Context API で代替しようとすれば可能だが、良いかどうかは別の話」が、今のところの所感です。