React.memo より useMemo が良さそう
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ React.memo だと children などに JSX の要素が入るとメモできない
◆ 毎回新たにオブジェクトを作る
◆ props の比較が毎回異なる
◆ メモできるようにするには children などをさらにメモする必要あり
◆ それなら React.memo せず useMemo で全体をメモするほうが良い
◆ 毎回新たにオブジェクトを作る
◆ props の比較が毎回異なる
◆ メモできるようにするには children などをさらにメモする必要あり
◆ それなら React.memo せず useMemo で全体をメモするほうが良い
コンポーネントの不要なレンダリングを減らすのは React.memo でも useMemo でもできますが React.memo のほうが コンポーネント内の書き方を変える必要がなくて見た目的には好きです
それにフックを書く関数の前半に JSX で DOM 定義があるよりも最後の return の中に全てあったが方がわかりやすいです
React.memo でコンポーネントをラップすると そのコンポーネントに渡される予定の props の中身をそれぞれ比較して 違いがなければそのコンポーネントのレンダリングをスキップしてくれます
c1 のボタンを押すと A, B, C が出力されます
memo されてる Component2M は value と name に変化がないので関数が呼び出されていません
Component1M は value に変化があるので関数が呼び出されます
Component2 は value に変化はないですが memo してないので毎回呼び出されます
c2 のボタンを押すと A, B, D が出力されます
この仕組みを見ていて思ったのですが children って props の比較でどう扱われるのでしょうか?
例えばこういう JSX です
常に props に変化はなく固定ですが div で作られるオブジェクトは毎回異なる気がします
試してみました
ボタンを押すと毎回 render というログが出力されています
やはり props 比較で毎回異なると扱われてるようです
対処するには children を毎回同じにする必要があります
方法は useState, useMemo, useRef など色々考えられますが children だけ memo しておくのはなんか微妙な感じがします
children 部分が別のところに移動しますし ComponentM みたいなコンポーネントを複数使いたいときにわかりづらくなります
また ComponentM 自体が React.memo を使ったものなので 2 回メモが必要になってます
こうするくらいなら最初から Component 全体を useMemo で保持して React.memo は使わないほうが良い気がします
その他の props を渡したり children の JSX 内で変数値を使うなら依存配列に追加します
children に限定しましたが 属性風に
と書いた場合も JSX 部分で毎回異なるオブジェクトになるので 同じようにメモする必要があります
どうしても useMemo が嫌なら JSX の要素を含まない形のコンポーネントを挟むということもできます
こういうふうに useMemo を使うコンポーネントの場合
div 要素があるせいで Component のメモが複雑化します
なので JSX の要素を props で受け取らないコンポーネントを作って これを React.memo でラップします
このコンポーネントの props には JSX の要素は入らないので props 比較で変更されてないことを正しく検知できます
使う側はこういう感じです
それにフックを書く関数の前半に JSX で DOM 定義があるよりも最後の return の中に全てあったが方がわかりやすいです
React.memo でコンポーネントをラップすると そのコンポーネントに渡される予定の props の中身をそれぞれ比較して 違いがなければそのコンポーネントのレンダリングをスキップしてくれます
<!DOCTYPE html>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useMemo } = React
const Component1 = ({ name, value }) => {
console.log(name, "1")
return (
<div>{name} {value}</div>
)
}
const Component2 = ({ name, value }) => {
console.log(name, "2")
return (
<div>{name} {value}</div>
)
}
const Component1M = React.memo(Component1)
const Component2M = React.memo(Component2)
const App = () => {
const [c1, setC1] = useState(0)
const [c2, setC2] = useState(0)
return (
<div>
<button onClick={() => setC1(c1 + 1)}>c1:{c1}</button>
<button onClick={() => setC2(c2 + 1)}>c2:{c2}</button>
<Component1 value={c1} name="A" />
<Component2 value={c2} name="B" />
<Component1M value={c1} name="C" />
<Component2M value={c2} name="D" />
</div>
)
}
ReactDOM.render(
<App/>,
document.getElementById("root")
)
</script>
<div id="root"></div>
c1 のボタンを押すと A, B, C が出力されます
memo されてる Component2M は value と name に変化がないので関数が呼び出されていません
Component1M は value に変化があるので関数が呼び出されます
Component2 は value に変化はないですが memo してないので毎回呼び出されます
c2 のボタンを押すと A, B, D が出力されます
この仕組みを見ていて思ったのですが children って props の比較でどう扱われるのでしょうか?
例えばこういう JSX です
<Component>
<div>A</div>
</Component>
常に props に変化はなく固定ですが div で作られるオブジェクトは毎回異なる気がします
試してみました
<!DOCTYPE html>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useMemo } = React
const Component = ({ children, value }) => {
console.log("render")
return (
<div>
<div>{value}</div>
<div>{children}</div>
</div>
)
}
const ComponentM = React.memo(Component)
const App = () => {
const [c, setC] = useState(0)
return (
<div>
<button onClick={() => setC(c + 1)}>c:{c}</button>
<ComponentM>
<div>text</div>
</ComponentM>
</div>
)
}
ReactDOM.render(
<App/>,
document.getElementById("root")
)
</script>
<div id="root"></div>
ボタンを押すと毎回 render というログが出力されています
やはり props 比較で毎回異なると扱われてるようです
対処するには children を毎回同じにする必要があります
方法は useState, useMemo, useRef など色々考えられますが children だけ memo しておくのはなんか微妙な感じがします
const App = () => {
const [c, setC] = useState(0)
const component_children = useMemo(() => <div>text</div>, [])
return (
<div>
<button onClick={() => setC(c + 1)}>c:{c}</button>
<ComponentM>
{component_children}
</ComponentM>
</div>
)
}
children 部分が別のところに移動しますし ComponentM みたいなコンポーネントを複数使いたいときにわかりづらくなります
また ComponentM 自体が React.memo を使ったものなので 2 回メモが必要になってます
こうするくらいなら最初から Component 全体を useMemo で保持して React.memo は使わないほうが良い気がします
<!DOCTYPE html>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useMemo } = React
const Component = ({ children, value }) => {
console.log("render")
return (
<div>
<div>{value}</div>
<div>{children}</div>
</div>
)
}
const App = () => {
const [c, setC] = useState(0)
const component = useMemo(() => (
<Component>
<div>text</div>
</Component>
), [])
return (
<div>
<button onClick={() => setC(c + 1)}>c:{c}</button>
{component}
</div>
)
}
ReactDOM.render(
<App/>,
document.getElementById("root")
)
</script>
<div id="root"></div>
その他の props を渡したり children の JSX 内で変数値を使うなら依存配列に追加します
const component = useMemo(() => (
<Component value={value}>
<div>{text}</div>
</Component>
), [text, value])
children に限定しましたが 属性風に
<Component
header={<div>header</div>}
body={<div>body</div>}
/>
と書いた場合も JSX 部分で毎回異なるオブジェクトになるので 同じようにメモする必要があります
どうしても useMemo が嫌なら JSX の要素を含まない形のコンポーネントを挟むということもできます
こういうふうに useMemo を使うコンポーネントの場合
const component = useMemo(() => (
<Component value={value}>
<div>{text}</div>
</Component>
), [value, text])
return (
<div>
{component}
</div>
)
div 要素があるせいで Component のメモが複雑化します
なので JSX の要素を props で受け取らないコンポーネントを作って これを React.memo でラップします
const ComponentWrapper = ({ value, text }) => {
return (
<Component value={value}>
<div>{text}</div>
</Component>
)
}
const ComponentWrapperM = React.memo(ComponentWrapper)
このコンポーネントの props には JSX の要素は入らないので props 比較で変更されてないことを正しく検知できます
使う側はこういう感じです
return (
<div>
<ComponentWrapperM value={value} text={text} />
</div>
)