意図しないタイミングで大量に再レンダリングが発生
useEffectやuseMemoを使う際、複雑な構造のObjectやArray型を第2引数に指定すると意図しないタイミングで再レンダリングが走ってしまうことが多々あったため、深い比較(DeepCompare)をするためのCustomHookを作ってみました。
なぜ意図しないタイミングで再レンダリングされるのか?
以下の記事で丁寧にまとめられているため詳しい解説は割愛しますが、要するにReactHooksの内部で ===
での比較がされているのが原因でレンダリングが発生してしまうものを、今回はCustomHookの中で深い比較に変えて対応してしまおうという形です。
前提環境
- react@17.0.2
- 深い比較には
fast-deep-equal@3.1.3
を使用 - 複雑なデータをフロントで扱わざるを得ないシステムなため、useEffectやuseMemoには多次元配列や深くネストしたObjectを構わず指定していく
実際のコード
共通で使う関数
import * as React from 'react'
import * as deepEqual from 'fast-deep-equal'
import { DependencyList } from 'react'
// 以下ページのコードをそのまま利用
// https://stackoverflow.com/questions/54095994/react-useeffect-comparing-objects
function useDeepCompareMemorize(value: DependencyList[number]) {
const ref = React.useRef()
if (!deepEqual(value, ref.current)) {
ref.current = value
}
return ref.current
}
deepEqualでの比較を用いたuseEffect
export function useDeepCompareEffect(
callback: React.EffectCallback,
dependencies: DependencyList
) {
// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(callback, dependencies.map(useDeepCompareMemorize))
}
deepEqualでの比較を用いたuseMemo
export function useDeepCompareMemo<T>(
callback: () => T,
dependencies: DependencyList
) {
// eslint-disable-next-line react-hooks/exhaustive-deps
return React.useMemo(callback, dependencies.map(useDeepCompareMemorize))
}
deepEqualでの比較を用いたuseEffectで、かつ初回の実行を無効にしたもの
export const useDeepCompareUpdateEffect = (
callback: () => void,
dependencies: DependencyList
) => {
const isFirstRender = React.useRef(true)
useDeepCompareEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false
return
}
return callback()
}, dependencies)
}
使い方
普段useEffectやuseMemoを使うのと同じように使います。
const obj = { hoge: 'hoge', fuga: 'fuga', nest: { hoge: 'hoge', fuga: 'fuga' } }
useDeepCompareEffect(() => {
// do something
}, [obj])
useDeepCompareUpdateEffect(() => {
// do something
}, [obj])
const value = useDeepCompareMemo(() => {
// do something
}, [obj])