概要
- 効率的なレンダリングを実現するにはコンポーネントをMemo化する必要がある
- ただし、約束事を守らないとMemo化したつもりでも実際に無駄な再レンダリングが走ることがある
- コンポーネントをMemo化するときの注意点と性質についてメモする
結論
コンポーネントをMemo化するときに知っておくべきこと
Reactの再レンダリング有無の判定はshallowEqualで行われる。よって、objectやlist, functionといったpropsはMemo化しておく必要があり、一方でprimitiveな値はMemo化が不要である。
props's type | rendaring behavior |
---|---|
primitive(string, number, boolean) | Memo化しなくても良い |
object | useMemoによるMemo化が必要 |
list | useMemoによるMemo化が必要 |
function | useCallbackによるMemo化が必要 |
boolean(他の変数を使って作成したもの) | Memo化しなくても良い |
コンポーネントをMemo化するにあたり意識しておくことまとめ
- Memo化したいコンポーネント(以降ChildComponentとする)をReact.memoでwrappする
- ChildComponentに渡すcallbackやobjectのパラメータを、useCallbackやuseMemoを使ってMemo化する
- primitive型やある変数を使って生成したbool値は特にmemo化は不要
簡単なコンポーネントを作り動作確認をしてみる
- ChildA: React.memoなし
- ChildB: React.memoあり
'use client';
import React, { useCallback, useMemo } from 'react';
import { useState } from 'react';
// types.ts
interface InputProps {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
priString: string,
priNumber: number,
priBool: boolean,
testObj: {
id: string,
name: string,
};
testBool: boolean,
testList: number[],
}
interface FormData {
childA: string;
childB: string;
}
// ChildA.tsx
const ChildA: React.FC<InputProps> = ({ value, onChange, priString, priNumber, priBool, testObj, testBool, testList }) => {
console.log("Rendering ChildA.")
return (
<div className="flex flex-col gap-2">
<label
htmlFor="childA"
className="text-sm font-medium"
>
Child A: {priString} {priNumber} {priBool ? "true": "false"} {testObj.id} {testObj.name} {testBool ? "true": "false"} {testList.length}
</label>
<input
id="childA"
name="childA"
type="text"
value={value}
onChange={onChange}
placeholder="Enter Child A"
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
);
};
// ChildB.tsx
const ChildB: React.FC<InputProps> = React.memo(({ value, onChange, priString, priNumber, priBool, testObj, testBool, testList }) => {
console.log("Rendering ChildB.")
return (
<div className="flex flex-col gap-2">
<label
htmlFor="childB"
className="text-sm font-medium"
>
Child B: {priString} {priNumber} {priBool ? "true": "false"} {testObj.id} {testObj.name} {testBool ? "true": "false"} {testList.length}
</label>
<input
id="childB"
name="childB"
type="text"
value={value}
onChange={onChange}
placeholder="Enter Child B"
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
);
});
export const Home: React.FC = () => {
const [formData, setFormData] = useState<FormData>({
childA: '',
childB: ''
});
const priStirng = "test-text"
const priNumber = 100
const priBool = true
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>): void => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: value
}));
}, []);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
console.log('Form submitted:', formData);
// Handle form submission here
};
const testObj = useMemo(() => ({
id: "10",
name: "Sato"
}), [])
const testList = useMemo(() => ([1,2,3,4]), [])
const testBool = priNumber > 99 && formData.childA == "aaa"
return (
<div className="w-full max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
<h2 className="text-xl font-bold mb-6">Input Form</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<ChildA
value={formData.childA}
onChange={handleInputChange}
testObj={testObj}
testList={testList}
priString={priStirng}
priNumber={priNumber}
priBool={priBool}
testBool={testBool}
/>
<ChildB
value={formData.childB}
onChange={handleInputChange}
testObj={testObj}
testList={testList}
priString={priStirng}
priNumber={priNumber}
priBool={priBool}
testBool={testBool}
/>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors"
>
Submit
</button>
</form>
</div>
);
};
export default Home;
- (ケースA) ChildAを更新時、ChildAのみレンダリングされる(ChildBはレンダリングされない)
- (ケースB) ChildBを更新時、ChildAとChildBがレンダリングされる
- handleInputChangeのuseCallbackやtestObjのuseMemoを削除した場合、ケースAにおいてChildAとChildBの両方がレンダリングされる