Dan Abramov氏による記事「Before You memo()」が2月23日付で公開されました。描画を最適化するために、memo
やuseMemo
を使うことは有効です。けれど、その前に考えられる基本的な手法をふたつ紹介されています。記事に示されたコード例を、かいつまんでご説明してみます。
重たいコンポーネントが考えなしに放り込まれたアプリケーション
はじめにとりあげられるのが、重たいコンポーネントを考えなしに放り込んだつぎのコード例です。ExpensiveTree
が負荷の高いコンポーネントを意味します。
import { useState } from 'react';
export default function App() {
let [color, setColor] = useState('red');
return (
<div>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>
<ExpensiveTree />
</div>
);
}
コード例のコンポーネントExpensiveTree
は、performance.now()
で意図的に負荷を高めています。前掲のコンポーネントApp
は、状態が変わるたびに、この重たいコンポーネントも再描画しなければならないわけです。
function ExpensiveTree() {
let now = performance.now();
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return <p>I am a very slow component tree.</p>;
}
Dan Abramov氏は、この作例からはじまるステップごとに、コードをCodeSandboxに公開しています。
状態を下層に移す
そこで、この問題に対処するひとつめの手法です。状態とその操作を子コンポーネント(Form
)に切り分けて、下層に移します。すると、親コンポーネントからは状態がなくなり、子コンポーネントだけが再描画すれば済むのです。
export default function App() {
// let [color, setColor] = useState('red');
return (
<div>
{/* <input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p> */}
<Form />
</div>
);
}
function Form() {
let [color, setColor] = useState('red');
return (
<>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>
</>
);
}
こちらがCodeSandboxの作例です。
親に状態をもたせたいとき
問題は、親に状態をもたせたいときです。このような例として、つぎのコードが示されています(作例)。
export default function App() {
let [color, setColor] = useState('red');
return (
// <div>
<div style={{ color }}>
<input value={color} onChange={(e) => setColor(e.target.value)} />
{/* <p style={{ color }}>Hello, world!</p> */}
<p>Hello, world!</p>
<ExpensiveTree />
</div>
);
}
この場合に使えるふたつめの手法です。やはり、状態は親から子コンポーネント(ColorPicker
)に切り出します。そして、負荷の高いコンポーネントや要素もその中に包んでしまうのです。
export default function App() {
// let [color, setColor] = useState('red');
return (
// <div style={{ color }}>
<ColorPicker>
{/* <input value={color} onChange={(e) => setColor(e.target.value)} /> */}
<p>Hello, world!</p>
<ExpensiveTree />
{/* </div> */}
</ColorPicker>
);
}
子コンポーネント(ColorPicker
)の状態とその操作は、前の例と変わりません。注目いただきたいのは、コンポーネントが親からchildren
プロパティを受け取ることです。そして、このプロパティは、状態をもたない親が変更することはありません。その場合、子コンポーネントの中でもchildren
の再描画は行われないのです。
function ColorPicker({ children }) {
let [color, setColor] = useState('red');
return (
<div style={{ color }}>
<input value={color} onChange={(e) => setColor(e.target.value)} />
{children}
</div>
);
}
これで、重たいコンポーネントの再描画は避けることができました。こちらが、CodeSandboxの作例です。変化するものとしないものを分けるという基本的な考え方が、このふたつの手法に通じているといえるでしょう。