React:useContextを使ったときの再レンダリングの抑制方法
https://1.bp.blogspot.com/-b5FMDU7DvWs/YMVaXu_F_XI/AAAAAAABd4M/NC2sTucmgqM7qspnIqC9FiZJfEGEDQ60wCNcBGAsYHQ/s400/animal_sai.png
React公式の useState / useContext を組み合わせたサンプルコードが以下から参照できる この手法を使用すると、setXXXX で state を変更した場合に、Context.Provider 配下のコンポーネントがごそっと再レンダリングされる
場合によっては再レンダリングコストが無視できなくなる場合があるかもしれない
基本的には再レンダリングされること自体は悪いことではない
課題認識→計測→最適化 の流れを経てアプリを最適化していくほうが良さそうという考え方をしている
あるいは遅くなる見込みが自明なので予め仕込んでおくとか言う手もありそう。
好みやコスト、リソースを総合して判断すべし。
忘備録兼ねてサンプルコードを用意しておく
無関係のコンポーネントが再レンダリングされる例
下記の例は Context が変化したとき useContext を使っているコンポーネントも使っていないコンポーネントも含めて Provider 配下の子要素がすべて再レンダリングされる
Context.Provider の value が変化したとき、配下のコンポーネントの再レンダリングが行われるためそういう仕様になっている
何らかの理由で巨大なReactアプリを作っている場合、再レンダリングのコストを無視できない場合もあるかも知れない。
なお仮に最適化した後、孫要素が Context に依存する場合はそこから配下は必要に応じて再レンダリングされるのでそこは安心して良い
code:ts
import React, { createContext, useContext, useState } from "react"
const Ctx = createContext({
state: false,
setState: (v: boolean) => {},
})
const Hoge = () => {
console.info('Hoge')
const ctx = useContext(Ctx)
return <button onClick={() => ctx.setState(!ctx.state)}>Hoge</button>
}
const Fuga = () => {
console.info('Fuga')
const ctx = useContext(Ctx)
return (<div>{ ctx.state ? 'TRUE' : 'FALSE'}</div>)
}
const Piyo = () => {
console.warn('Piyo (oops!)') // 再レンダリングしてほしくないときの出力
return <div>Piyo</div>
}
export default function Root({}) {
return (
<div>
<Ctx.Provider value={{state, setState}}>
<Hoge />
<Fuga />
<Piyo />
</Ctx.Provider>
</div>
)
}
Rootの子要素をすべてuseMemoでメモ化して抑制する例
Root の Provider の子要素を React:useMemo でメモ化すると無関係なものを含めた子要素全体の再レンダリングを抑制できる すでにあるコードを雑に最適化しようとしたときにとれるおそらく一番簡単な手法
code:ts
// -- Hoge, Fuga, Piyo はベースと同じなので略 --
export default function Root({}) {
const children = useMemo(() => (<>
<Hoge />
<Fuga />
<Piyo />
</>), [])
return (
<div>
<Ctx.Provider value={{state, setState}}>
{children}
</Ctx.Provider>
</div>
)
}
再描画したくないコンポーネントだけをメモ化する例
再描画してほしくないコンポーネントをピンポイントで useMemo でメモ化する
Piyo は Context の変化に影響されないことを明示する手法
code:ts
// -- 他のコードはベースと同じなので略 --
const Piyo = () => useMemo(() => {
console.warn('Piyo (oops!)')
return <div>Piyo</div>
}, [])
「ん〜・・・渋くない?」という場合
Recoil あるいは Redux を使って React.Context を使わない状態管理を使ってみるといいかも とはいえこちらも使い方によって再レンダリングが起こることはある
どれが絶対的な正解ということは無いのでチームや個人の成熟度、目的に合わせて適切な技術を選定しましょう
関連情報