React: メモ化の前に考えるふたつの基本的な手法

[Dan Abramov](https://mobile.twitter.com/dan_abramov)氏による記事「[Before You memo()](https://overreacted.io/before-you-memo/)」が2月23日付で公開されました。描画を最適化するために、`memo`や`useMemo`を使うことは有効です。けれど、その前に考えられる基本的な手法をふたつ紹介されています。記事に示されたコード例を、かいつまんでご説明してみます。

# 重たいコンポーネントが考えなしに放り込まれたアプリケーション
はじめにとりあげられるのが、重たいコンポーネントを考えなしに放り込んだつぎのコード例です。`ExpensiveTree`が負荷の高いコンポーネントを意味します。

```react
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()`](https://developer.mozilla.org/docs/Web/API/Performance/now)で意図的に負荷を高めています。前掲のコンポーネント`App`は、状態が変わるたびに、この重たいコンポーネントも再描画しなければならないわけです。

```react
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氏は、この[作例](https://codesandbox.io/s/frosty-glade-m33km?file=/src/App.js:23-513)からはじまるステップごとに、コードをCodeSandboxに公開しています。


# 状態を下層に移す
そこで、この問題に対処するひとつめの手法です。状態とその操作を子コンポーネント(`Form`)に切り分けて、下層に移します。すると、親コンポーネントからは状態がなくなり、子コンポーネントだけが再描画すれば済むのです。

```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> */}
			<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の[作例](https://codesandbox.io/s/billowing-wood-1tq2u?file=/src/App.js:64-380)です。


# 親に状態をもたせたいとき
問題は、親に状態をもたせたいときです。このような例として、つぎのコードが示されています([作例](https://codesandbox.io/s/bold-dust-0jbg7?file=/src/App.js:58-313))。

```react
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`)に切り出します。そして、負荷の高いコンポーネントや要素もその中に包んでしまうのです。

```react
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`](https://ja.reactjs.org/docs/glossary.html#propschildren)プロパティを受け取ることです。そして、このプロパティは、状態をもたない親が変更することはありません。その場合、子コンポーネントの中でも`children`の再描画は行われないのです。

```react
function ColorPicker({ children }) {
	let [color, setColor] = useState('red');
	return (
		<div style={{ color }}>
			<input value={color} onChange={(e) => setColor(e.target.value)} />
			{children}
		</div>
	);
}
```

これで、重たいコンポーネントの再描画は避けることができました。こちらが、CodeSandboxの[作例](https://codesandbox.io/s/bold-dust-0jbg7?file=/src/App.js:58-313)です。変化するものとしないものを分けるという基本的な考え方が、このふたつの手法に通じているといえるでしょう。