Reactを学んでいる中でいまいち理解できているかわからなかったことを、自分なりに整理してみました。
今回は、Reactの再レンダリングの条件からレンダリングを最適化するために使用されるReact memoとuseCallbackを扱います。
そもそもレンダリングとは
レンダリングを「ページ再読み込み」と理解していたのですが、その理解はおおむね間違っていないようでした。
もう少し詳しく調べてみると、レンダリングとは何かしらのデータを最終的な形式に変換するプロセスのことを一般的には指すようです。
Webアプリケーションで使うレンダリングの場合は、WebページのHTMLやCSS, Javascriptなどのデータをブラウザ側に渡してユーザーが見るページを作成するプロセスのことをいいます。
React で再レンダリングが起きる条件
Reactを使用する開発で再レンダリングは以下の3つの条件下で起こります。
- Stateが更新されたとき、クラスや関数コンポーネントは再レンダリングされます
- propsを親コンポーネントから子コンポーネントへ渡した時に、親側でpropsが変更されたときは子コンポーネントも再レンダリングされます
- 親コンポーネントが再レンダリングされるとその配下にある子コンポーネントも再レンダリングされます(propsを受け取っていなくても関係ない)
React memo
Reactのmemoは親のコンポーネントに変更があっても、子コンポーネントに渡しているpropsに変更がない場合、子コンポーネントを再レンダリングさせないようにするため使用します。
基本的な使い方は、親からpropsを受け取っている子の関数コンポーネントをラップする形で使います。
親コンポーネント
export const Parent = () => {
const [props, setProps] = useState("");
const onClickButton = () => {
setProps = (e) => setProps(e.target.value);
}
return (
<button onClickButton={onClickButton}>ボタンをクリック</button>
<Child props={props} />
)
}
子コンポーネント
export const Child = memo(({ props }) => {
return (
<p>子コンポーネントで{props}を受け取りました<p>
)
})
上記の例でいうと、親コンポーネント側でボタンがクリックされた時にsetPropsでpropsの中身が変わるようになっています。
memoを使用することでpropsの中身が実際に変更になる場合のみ、子コンポーネントがレンダリングされるようにできます。
useCallback
memoと一緒によく使用するのがuseCallbackとなります。
以下の例を見てください。
親コンポーネント
const ParentComponent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const incrementCount = () => {
setCount((prevCount) => prevCount + 1);
};
const onChangeText = () => {
setText((e) => e.target.value);
}
return (
<ChildComponent onClick={incrementCount} />
<input onChange={onChangeText}>
);
};
子コンポーネント
export const ChildComponent = memo(({ onClick }) => {
return (
<button onClick={onClick}>子コンポーネントのボタン</button>
)
})
上記の例で言うと、親コンポーネント(ParentComponent)が初めてレンダリングされると、incrementCount関数が定義されて、子コンポーネント(ChildComponent)にonClickとしてpropsが渡されます。
そのあと、inputの部分でテキストが入力されると親コンポーネントが再度レンダリングされます。
この時、子コンポーネントをmemoでラップしていても、子コンポーネントの方はonClickでincrementCount関数が呼び出されておらず中身が変わっていないにも関わらず、再度レンダリングされてしまいます。
このような不要な再レンダリングを防ぐために、incrementCount関数でuseCallbackを使用します。
親コンポーネントでuseCallbackを使用
const ParentComponent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const incrementCount = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
const onChangeText = () => {
setText((e) => e.target.value);
}
return (
<ChildComponent onClick={incrementCount} />
<input onChange={onChangeText}>
);
};
子コンポーネント
export const ChildComponent = memo(({ onClick }) => {
return (
<button onClick={onClick}>子コンポーネントのボタン</button>
)
})
useCallbackの第二引数には依存配列をおきます。空の場合は関数は一度だけ実行されるので親コンポーネントの他の関数でステータス等の変更があってもincrementCount関数の部分は再生成されずに再利用されます。
上記のようにuseCallbackを使用すると、memoでラップされた子コンポーネントはpropsとして渡されているonClickの中身が変わらない限り再レンダリングされません。
おわりに
学んだばかりのレンダリングやmemoとuseCallbackの使い分けがいまいち理解できていませんでしたが、今回まとめていくなかでそのあたりの使い分けをする理由がわかり理解を進めることができました。
Reactを勉強中の方の参考になれば幸いです。
JISOUのメンバー募集中
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
実践的なカリキュラムで、あなたのエンジニアとしてのキャリアを最短で飛躍させましょう!
興味のある方は、ぜひホームページからお気軽にカウンセリングをお申し込みください!
▼▼▼