Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
React.memoがシャロー比較を使って変更されていない子コンポーネントのレンダリングをスキップする方法。
デフォルトでは、Reactはコンポーネント自体のpropsが変わったかどうかに関係なく、親コンポーネントが再レンダリングされるたびにコンポーネントを再レンダリングします。React.memoは関数コンポーネントをラップしてレンダリング出力をメモ化する高階コンポーネントです。前のpropsと新しいpropsのシャロー比較を行い、それらが等しい場合は再レンダリングをスキップします。
親コンポーネントのstateが変更されると、ReactはParentの関数を呼び出します。その後、返されたすべてのJSX childrenを処理します — propsが同一でも関数を呼び出します。これがツリー全体に伝播します。
React.memo(Component)は新しいコンポーネントを返します。ComponentのFunctionを呼び出す前に、Reactは新しいpropsが前のpropsとシャローに等しいかどうかをCheckします。等しい場合、Reactは前のレンダリング出力を再利用し、関数呼び出しを完全にスキップします。
各propキーに対して、ReactはoldProp === newPropを比較します。プリミティブ(string、number、boolean)は値で比較されます。オブジェクトと関数は参照で比較されます — propsに新しいオブジェクトリテラル'{''}'は常に等値チェックに失敗します。
React.memo(Component, arePropsEqual)は第2引数としてカスタム比較関数を受け取ります。trueを返すと再レンダリングをスキップ(propsは等しい)、falseを返すと許可します。深い等値Checkや特定のpropsを無視する場合に役立ちます。
React.memoはpropの変更による再レンダリングのみをスキップします。コンポーネントがuseContext()を呼び出す場合、React.memoに関係なく、context値が変わると再レンダリングされます。
コンポーネントのレンダリング出力をメモ化するHOC。propsが前のレンダリングとシャローに等しい場合、再レンダリングをスキップします。
各propをObject.is()で比較します。プリミティブに効果的。オブジェクトと関数はレンダリングをまたいで同じ参照を維持する必要があります。
React.memoがpropsが等しいと判断したとき、前のレンダリング出力を返してコンポーネント関数の呼び出しをスキップします。
レンダリングをまたいで同じオブジェクト・関数参照を保持すること。プリミティブ以外のpropsでReact.memoを正しく機能させるために必要です。
前のレンダリングと出力が同一の再レンダリング。propsが論理的に変わっていない場合、React.memoはこれらを防ぎます。
1// Without memo — re-renders every time parent renders2function ExpensiveList({ items }) {3 return items.map(item => <Item key={item.id} {...item} />);4}56// With memo — skips re-render if items reference is stable7const ExpensiveList = React.memo(function ExpensiveList({ items }) {8 return items.map(item => <Item key={item.id} {...item} />);9});1011function Parent() {12 const [count, setCount] = useState(0);1314 // ❌ New array every render — memo never helps15 const items = [{ id: 1, name: 'Apple' }];1617 // ✅ Stable reference — memo works as expected18 const items = useMemo(() => [{ id: 1, name: 'Apple' }], []);1920 return (21 <>22 <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>23 <ExpensiveList items={items} /> {/* skips re-render when only count changes */}24 </>25 );26}
複雑なコンポーネントを持つ大規模なReactツリーでは、不要な再レンダリングが積み重なって実際のパフォーマンス問題になります — 遅い操作、フレームのドロップ、アニメーションのラグ。React.memoを使えば、特定のコンポーネントが再レンダリングされることを外科的に防ぎ、高コストなサブツリーを安定させながらアプリの残りの部分が自由に更新できます。
A Slack-like app renders a message list. When a user types in the input box, the entire list of 1000+ message components re-renders because the parent (ChatRoom) updates on every keystroke.
Each keystroke updates the parent's `inputValue` state, triggering a re-render. Without React.memo, all 1000 Message components re-render even though none of their props changed. Each Message component does string formatting and date calculations — the combined cost creates visible lag.
Wrap Message in React.memo: `const Message = React.memo(({ text, author, timestamp }) => { ... })`. Since the message props are primitive values (strings, numbers), React.memo's shallow comparison correctly identifies that nothing changed. The 1000 messages are skipped entirely — only the input re-renders.
Takeaway: React.memo shines when a parent re-renders frequently but a child's props rarely change. The ideal candidates are list items, complex charts, and any component with expensive rendering where the parent updates for unrelated reasons.
A MapWidget receives a `coordinates` object `{ lat: 40.7, lng: -74.0 }`. Despite wrapping in React.memo, it still re-renders because the parent creates a new coordinates object each render.
React.memo uses shallow comparison by default: `oldCoords !== newCoords` is always true because each render creates a new object, even if lat/lng values are identical. Shallow comparison checks reference equality, not value equality.
Provide a custom comparison: `React.memo(MapWidget, (prev, next) => prev.coordinates.lat === next.coordinates.lat && prev.coordinates.lng === next.coordinates.lng)`. Now React.memo compares the actual values instead of the object reference. Alternatively, flatten the props: `<MapWidget lat={40.7} lng={-74.0} />` — primitives compare by value automatically.
Takeaway: Use custom areEqual functions for components that receive objects or arrays as props and can't be easily memoized upstream. But first, consider if you can flatten the props to primitives or memoize the object with useMemo in the parent — these are simpler and more maintainable.