メモ化でReactのパフォーマンスチューンイングを行う
2022-03-13
2022-03-13
8 min read
メモ化とは
Reactでパフォーマンスチューニングを行う方法としてメモ化というものがあります。
メモ化を利用することで、コンポーネントのレンダリングを制御し、UXの向上
に繋げることができます。言い換えると、無駄なレンダリングを無くして、動きを軽くするということです。これを適所に正しく用いることで、重たい処理がある画面でもサクサクした操作が期待できます。
再レンダリングについて
メモ化の具体的な内容に触れる前に、まずReactの再レンダリングについて考えたいと思います。
Reactで再レンダリングされるタイミングは、下記のような場合です。
stateが更新された時
propsが更新された時
また、再レンダリングされたコンポーネントの配下の子要素は再レンダリングされる
ので、たくさんのコンポーネントを組み合わせて作った画面はでは、このことを頭に入れておく必要があります。
ちなみにReactでは、コンポーネントベースで開発を行うことが多いと思います。コンポーネント設計についてはこちらの記事「Atomic Designを用いたReactのコンポーネント設計」で記述してるので、ご興味がある方はぜひ読んでみてください!
Reactでメモ化を行う方法
Reactでメモ化を行うには下記の関数を使用します。
memo(React.memo)
useCallback
useMemo
それでは使い方や挙動について1つずつ確認していきます。
memoとは
React.memoはコンポーネントのメモ化
を行うことができます。
これを利用することで、親のコンポーネントがstateの変更などで再レンダリングされても、子のコンポーネントに影響がなければ再レンダリングを防ぐことができます(全文で触れた「再レンダリングされたコンポーネントの配下の子要素は再レンダリングされる」という場面です)。
以下サンプルコードです。
//親コンポーネント
export default function App() {
const [value, setValue] = useState('')
const onChange = (e)=>setValue(e.target.value)
return (
<div className="App">
<input
type='text'
value={value}
onChange={onChange}
/>
<Children />
</div>
);
}
//子コンポーネント
const Children = () => {
let data = []
for(let i=0; i<3; i++){
console.log(i)
data.push(<p key={i}>{i}</p>)
}
return data
}
<初期表示>
<input入力後表示>
inputの値が入力される度に、関係のない子コンポーネントの値も際レンダリングされてしまっています。
ではReact.memoを使って再レンダリングを制御してみます。
//子コンポーネント
const Children = React.memo(() => {
let data = []
for(let i=0; i<3; i++){
console.log(i)
data.push(<p key={i}>{i}</p>)
}
return data
})
inputの値が入力されるされても、再レンダリングされていないことが確認できました!
useCallbackとは
useCallbackは関数のメモ化
を行うことができます。
それでは先程のコードを少し書き換えて、動きを見ていきます。
//親コンポーネント
export default function App() {
const [value, setValue] = useState('')
const onChange = (e)=>setValue(e.target.value)
const onClick = ()=>alert('ok')
return (
<div className="App">
<input
type='text'
value={value}
onChange={onChange}
/>
<Button onClick={onClick} />
</div>
);
}
//子コンポーネント
const Button = React.memo(({onClick}) => {
for(let i=0; i<3; i++){
console.log(i)
}
return(
<button
type="button"
onClick={onClick}
>ok</button>
)
})
子コンポーネントをボタンのコンポーネントにして、親のコンポーネントからproprでonClick関数を渡しています。
<input入力後表示>
先ほどのReact.memoによって子コンポーネントを制御しているにも関わらず、inputの値が入力される度に再レンダリングされてしまっています。
これは親から子コンポーネントに渡しているonClick関数が、レンダリングの度に再生成されているためです。useCallbackを用いて関数のメモ化を行うことで、子コンポーネントの再レンダリングを防ぐことができます。
ではuseCallbackを使って再レンダリングを制御してみます。
const onClick = useCallback(()=>alert('ok'),[])
変更内容はonClick関数をuseCallbackを使って定義するだけです。inputを入力してみると、再レンダリングが制御できてることが確認できます!
ちなみに第二引数には依存関係を指定することができます。
useMemoとは
useMemoは変数のメモ化
を行うことができます。
今回はuseMemoの動きの違いを比較できるように、下記のようなサンプルコードを記述します。
export default function App() {
const [count, setCount] = useState(0)
const onClick = ()=>setCount(count + 1)
const result1 = count + 10
const result2 = React.useMemo(()=>count + 10,[])
const result3 = React.useMemo(()=>count + 10,[count])
return (
<div className="App">
<button onClick={onClick}>+</button>
<p>result1:10 + {count} = {result1}</p>
<p>result2:10 + {count} = {result2}</p>
<p>result3:10 + {count} = {result3}</p>
</div>
);
}
カウントボタンが押下されると、+1されていく簡単なプログラムです。
<カウントボタン押下後>
違いをまとめると下記のようになります。
- result1
- レンダリングのたびに再計算される
- result2
- 初回レンダリングの時のみ計算される
- result3
- 依存関係に指定しているcountの値が変更される時計算される
重い計算処理などは、useMemoを使用してメモ化しておくことで、必要な時のみ計算されるように制御できます。
まとめ
メモ化はパフォーマンスの向上に繋がりますが、全てのコンポーネントをメモ化制御する必要はないと思います。
メモ化の内部的な動きは、レンダリングの前後の値を比較して、差分がない場合に不要なレンダリングを抑制するもの
です。メモ化を行う状況によっては、この差分の比較が大変であれば、パフォーマンスの低下につながる場合もあります。
個人的には最初からこの辺りまで細かく考えて実装するより、重そうな処理のとこや、実際に動かしてみて重いと感じたとこをメモ化していくくらいでいいのかなと思っています。