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 で代替しようとすれば可能だが、良いかどうかは別の話」が、今のところの所感です。