6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-24

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

重たいコンポーネントが考えなしに放り込まれたアプリケーション

はじめにとりあげられるのが、重たいコンポーネントを考えなしに放り込んだつぎのコード例です。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の作例です。変化するものとしないものを分けるという基本的な考え方が、このふたつの手法に通じているといえるでしょう。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?