memo๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ์˜ Props๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

React ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— memo์™€ ๋™์ผํ•œ ์ตœ์ ํ™”๋ฅผ ์ž๋™์œผ๋กœ ์ ์šฉํ•˜๋ฏ€๋กœ ์ˆ˜๋™์œผ๋กœ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ํ•  ํ•„์š”๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•ด ์ปดํฌ๋„ŒํŠธ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋ ˆํผ๋Ÿฐ์Šค

memo(Component, arePropsEqual?)

์ปดํฌ๋„ŒํŠธ๋ฅผ memo๋กœ ๊ฐ์‹ธ๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ๋ฉ”๋ชจ๋œMemoized ๋ฒ„์ „์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”๋ชจ๋œ ๋ฒ„์ „์˜ ์ปดํฌ๋„ŒํŠธ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜์–ด๋„ Props๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์€ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์ด์ง€, ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— React๋Š” ์—ฌ์ „ํžˆ ๋‹ค์‹œ ๋ Œ๋”๋ง๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

import { memo } from 'react';

const SomeComponent = memo(function SomeComponent(props) {
// ...
});

์•„๋ž˜ ์˜ˆ์‹œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜

  • Component: ๋ฉ”๋ชจMemoizeํ•˜๋ ค๋Š” ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. memo๋Š” ์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ๋Œ€์‹  ์ƒˆ๋กœ์šด ๋ฉ”๋ชจ๋œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜์™€ forwardRef ์ปดํฌ๋„ŒํŠธ๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ์œ ํšจํ•œ React ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • optional arePropsEqual: ์ปดํฌ๋„ŒํŠธ์˜ ์ด์ „ Props์™€ ์ƒˆ๋กœ์šด Props์˜ ๋‘ ๊ฐ€์ง€ ์ธ์ˆ˜๋ฅผ ๋ฐ›๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์ด์ „ Props์™€ ์ƒˆ๋กœ์šด Props๊ฐ€ ๋™์ผํ•œ ๊ฒฝ์šฐ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ด์ „ Props์™€ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ  ์ƒˆ๋กœ์šด Props์—์„œ๋„ ์ด์ „ Props์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ true๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด false๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์ด ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. React๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Object.is๋กœ ๊ฐ Props๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’

memo๋Š” ์ƒˆ๋กœ์šด React ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. memo์— ์ œ๊ณตํ•œ ์ปดํฌ๋„ŒํŠธ์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ, ๋ถ€๋ชจ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋˜๋”๋ผ๋„ Props๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ํ•œ React๋Š” ์ด๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.


์‚ฌ์šฉ๋ฒ•

Props๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์„ ๋•Œ ๋ฆฌ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋›ฐ๊ธฐ

React๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ถ€๋ชจ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋  ๋•Œ๋งˆ๋‹ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. memo๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ์ƒˆ๋กœ์šด Props๊ฐ€ ์ด์ „ Props์™€ ๊ฐ™์œผ๋ฉด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜๋”๋ผ๋„ React๊ฐ€ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฉ”๋ชจ๋œMemoized ์ƒํƒœ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฉ”๋ชจํ•˜๋ ค๋ฉด memo๋กœ ๊ฐ์‹ธ๊ณ  ๊ธฐ์กด ์ปดํฌ๋„ŒํŠธ ๋Œ€์‹ ์— ๋ฐ˜ํ™˜๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

const Greeting = memo(function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
});

export default Greeting;

React ์ปดํฌ๋„ŒํŠธ๋Š” ํ•ญ์ƒ ์ˆœ์ˆ˜ํ•œ ๋ Œ๋”๋ง ๋กœ์ง์„ ๊ฐ€์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Props, State ๊ทธ๋ฆฌ๊ณ  Context๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉด ํ•ญ์ƒ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. memo๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ด ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ค€์ˆ˜ํ•œ๋‹ค๊ณ  ์•Œ๋ฆฌ๋ฏ€๋กœ, Props๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ํ•œ React๋Š” ๋ฆฌ๋ Œ๋”๋ง ๋  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. memo๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ์ปดํฌ๋„ŒํŠธ์˜ State๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ ์ค‘์ธ Context๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ์—์„œ Greeting ์ปดํฌ๋„ŒํŠธ๋Š” name์ด Props ์ค‘ ํ•˜๋‚˜์ด๊ธฐ ๋•Œ๋ฌธ์— name์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋ฆฌ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ address๋Š” Greeting์˜ Props๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— address๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋Š” ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

import { memo, useState } from 'react';

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

const Greeting = memo(function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  return <h3>Hello{name && ', '}{name}!</h3>;
});

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

memo๋Š” ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. memo ์—†์ด ์ฝ”๋“œ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋จผ์ € ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋ฅผ ์ฐพ์•„์„œ ํ•ด๊ฒฐํ•˜์„ธ์š”. ์ดํ›„์— memo๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ธฐ

๋ชจ๋“  ๊ณณ์— memo๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ• ๊นŒ์š”?

If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful.

memo๋กœ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •ํ™•ํžˆ ๋™์ผํ•œ Props๋กœ ์ž์ฃผ ๋ฆฌ๋ Œ๋”๋ง ๋˜๊ณ , ๋ฆฌ๋ Œ๋”๋ง ๋กœ์ง์ด ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋  ๋•Œ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ์„ ๋งŒํผ์˜ ์ง€์—ฐ์ด ์—†๋‹ค๋ฉด memo๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. memo๋Š” ๊ฐ์ฒด ๋˜๋Š” ๋ Œ๋”๋ง ์ค‘์— ์ •์˜๋œ ์ผ๋ฐ˜ ํ•จ์ˆ˜์ฒ˜๋Ÿผ ํ•ญ์ƒ ๋‹ค๋ฅธ Props๊ฐ€ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌ๋˜๋Š” ๊ฒฝ์šฐ์— ์™„์ „ํžˆ ๋ฌด์šฉ์ง€๋ฌผ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ memo์™€ ํ•จ๊ป˜ useMemo์™€ useCallback์ด ์ข…์ข… ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ ์™ธ์˜ ๊ฒฝ์šฐ์—๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ memo๋กœ ๊ฐ์‹ธ๋Š” ์ด์ ์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ํ•ด์„œ ํฌ๊ฒŒ ํ•ด๊ฐ€ ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ถ€ ํŒ€์—์„œ๋Š” ๊ฐœ๋ณ„ ์‚ฌ๋ก€์— ๋Œ€ํ•ด ๊ณ ๋ คํ•˜์ง€ ์•Š๊ณ  ๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์ด ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•˜๋Š” ๋ฐฉ์‹์„ ์„ ํƒํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง„๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ชจ๋“  ๋ฉ”๋ชจ์ด์ œ์ด์…˜์ด ํšจ๊ณผ์ ์ด์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ํ•ญ์ƒ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฐ’์ด ํ•˜๋‚˜๋ผ๋„ ์žˆ๋‹ค๋ฉด, ์ปดํฌ๋„ŒํŠธ ์ „์ฒด์˜ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ์ค‘๋‹จํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ๋ช‡๊ฐ€์ง€ ์›์น™์„ ๋”ฐ๋ฅด๋ฉด ๋ฉ”๋ชจ์ด์ œ์ด์…˜์ด ๋ถˆํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ๊ฐ์Œ€ ๋•Œ JSX๋ฅผ ์ž์‹์œผ๋กœ ๋ฐ›์•„๋“ค์ด๋„๋ก ํ•˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ž˜ํผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์‹ ์˜ State๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ React๋Š” ๊ทธ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ์ง€์—ญ State๋ฅผ ์„ ํ˜ธํ•˜๊ณ  ํ•„์š” ์ด์ƒ์œผ๋กœ State ๋Œ์–ด์˜ฌ๋ฆฌ๊ธฐ๋ฅผ ํ•˜์ง€ ๋งˆ์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ตœ์ƒ์œ„ ํŠธ๋ฆฌ๋‚˜ ์ „์—ญ State ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํผ์ด๋‚˜ ์•„์ดํ…œ์ด ํ˜ธ๋ฒ„Hover๋˜์—ˆ๋Š”์ง€์™€ ๊ฐ™์€ ์ผ์‹œ์ ์ธ State๋ฅผ ๋‘์ง€ ๋งˆ์„ธ์š”.
  3. ๋ Œ๋”๋ง ๋กœ์ง์„ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”. ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ–ˆ์„ ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๋ˆˆ์— ๋„๋Š” ์‹œ๊ฐ์  ์•„ํ‹ฐํŒฉํŠธ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค๋ฉด ์ปดํฌ๋„ŒํŠธ์— ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค! ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•˜๋Š” ๋Œ€์‹  ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š”.
  4. State๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ถˆํ•„์š”ํ•œ Effect๋ฅผ ํ”ผํ•˜์„ธ์š”. React ์•ฑ์—์„œ ๋Œ€๋ถ€๋ถ„์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ๋ Œ๋”๋งํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” Effect์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ผ๋ จ์˜ ์—…๋ฐ์ดํŠธ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  5. Effect์—์„œ ๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ๋Œ€์‹ ์— ์ผ๋ถ€ ๊ฐ์ฒด๋‚˜ ํ•จ์ˆ˜๋ฅผ Effect ๋‚ด๋ถ€๋‚˜ ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€๋กœ ์ด๋™ํ•˜๋Š” ๊ฒƒ์ด ๋” ๊ฐ„๋‹จํ•  ๋•Œ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

ํŠน์ • ์ƒํ˜ธ์ž‘์šฉ์ด ์—ฌ์ „ํžˆ ๋А๋ฆฌ๊ฒŒ ๋А๊ปด์ง„๋‹ค๋ฉด React ๊ฐœ๋ฐœ์ž ๋„๊ตฌ Profiler๋ฅผ ์‚ฌ์šฉํ•ด ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ํ†ตํ•ด ๊ฐ€์žฅ ํฐ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•˜์„ธ์š”. ์ด๋Ÿฌํ•œ ์›์น™์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋” ์‰ฝ๊ฒŒ ๋””๋ฒ„๊น…ํ•˜๊ณ  ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋ฏ€๋กœ ์–ด๋–ค ๊ฒฝ์šฐ๋“  ์ด ์›์น™์„ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์žฅ๊ธฐ์ ์œผ๋กœ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ์™„์ „ํžˆ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์„ธ๋ถ„๋œ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์—ฐ๊ตฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.


State๋ฅผ ์‚ฌ์šฉํ•ด ๋ฉ”๋ชจ์ด์ œ์ด์…˜๋œ ์ปดํฌ๋„ŒํŠธ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜๋œ ๊ฒฝ์šฐ์—๋„, ์ปดํฌ๋„ŒํŠธ์˜ State๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ๋ฉ”๋ชจ์ด์ œ์ด์…˜์€ ๋ถ€๋ชจ์—์„œ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌ๋˜๋Š” Props์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

import { memo, useState } from 'react';

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

const Greeting = memo(function Greeting({ name }) {
  console.log('Greeting was rendered at', new Date().toLocaleTimeString());
  const [greeting, setGreeting] = useState('Hello');
  return (
    <>
      <h3>{greeting}{name && ', '}{name}!</h3>
      <GreetingSelector value={greeting} onChange={setGreeting} />
    </>
  );
});

function GreetingSelector({ value, onChange }) {
  return (
    <>
      <label>
        <input
          type="radio"
          checked={value === 'Hello'}
          onChange={e => onChange('Hello')}
        />
        Regular greeting
      </label>
      <label>
        <input
          type="radio"
          checked={value === 'Hello and welcome'}
          onChange={e => onChange('Hello and welcome')}
        />
        Enthusiastic greeting
      </label>
    </>
  );
}

State ๋ณ€์ˆ˜๋ฅผ ํ˜„์žฌ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜๋ฉด React๋Š” memo ์—†์ด๋„ ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•œ ๋ฒˆ ๋” ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ฒฐ๊ณผ๋Š” ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค.


Context๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจํ™”๋œ ์ปดํฌ๋„ŒํŠธ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฉ”๋ชจ๋˜์—ˆ๋”๋ผ๋„, ์‚ฌ์šฉ ์ค‘์ธ Context๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ๋ฉ”๋ชจ๋Š” ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋˜๋Š” Props์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

import { createContext, memo, useContext, useState } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  const [theme, setTheme] = useState('dark');

  function handleClick() {
    setTheme(theme === 'dark' ? 'light' : 'dark');
  }

  return (
    <ThemeContext value={theme}>
      <button onClick={handleClick}>
        Switch theme
      </button>
      <Greeting name="Taylor" />
    </ThemeContext>
  );
}

const Greeting = memo(function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  const theme = useContext(ThemeContext);
  return (
    <h3 className={theme}>Hello, {name}!</h3>
  );
});

์ผ๋ถ€ Context์˜ ์ผ์ • ๋ถ€๋ถ„์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋˜๋„๋ก ํ•˜๋ ค๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‘ ๊ฐœ๋กœ ๋‚˜๋ˆ ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์™ธ๋ถ€ ์ปดํฌ๋„ŒํŠธ์˜ Context์—์„œ ํ•„์š”ํ•œ ๋‚ด์šฉ์„ ์ฝ๊ณ , ๋ฉ”๋ชจํ™”๋œ ์ž์‹์—๊ฒŒ Prop์œผ๋กœ ์ „๋‹ฌํ•˜์„ธ์š”.


Props ๋ณ€๊ฒฝ ์ตœ์†Œํ™”ํ•˜๊ธฐ

memo๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์–ด๋–ค Prop๋“  ์ด์ „์˜ Prop๊ณผ ์–•์€ ๋น„๊ต ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์ง€ ์•Š์„ ๋•Œ๋งˆ๋‹ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰ React๋Š” Object.is ๋น„๊ต๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ ๋ชจ๋“  Prop์„ ์ด์ „ ๊ฐ’๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. Object.is(3, 3)๋Š” true์ด์ง€๋งŒ Object.is({}, {})๋Š” false์ž…๋‹ˆ๋‹ค.

memo๋ฅผ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๋ ค๋ฉด, Props๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ํšŸ์ˆ˜๋ฅผ ์ตœ์†Œํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Prop์ด ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ, useMemo๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ๋งค๋ฒˆ ๋‹ค์‹œ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜์„ธ์š”.

function Page() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);

const person = useMemo(
() => ({ name, age }),
[name, age]
);

return <Profile person={person} />;
}

const Profile = memo(function Profile({ person }) {
// ...
});

Props์˜ ๋ณ€๊ฒฝ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ Props์— ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ์ •๋ณด๋งŒ ๋ฐ›๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ „์ฒด ๊ฐ์ฒด ๋Œ€์‹  ๊ฐœ๋ณ„ ๊ฐ’์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function Page() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
return <Profile name={name} age={age} />;
}

const Profile = memo(function Profile({ name, age }) {
// ...
});

๋•Œ๋กœ๋Š” ๊ฐœ๋ณ„ ๊ฐ’๋„ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ ์ปดํฌ๋„ŒํŠธ๋Š” ๊ฐ’ ์ž์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฐ’์˜ ์กด์žฌ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋ถˆ๋ฆฌ์–ธ ๊ฐ’์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.

function GroupsLanding({ person }) {
const hasGroups = person.groups !== null;
return <CallToAction hasGroups={hasGroups} />;
}

const CallToAction = memo(function CallToAction({ hasGroups }) {
// ...
});

๋ฉ”๋ชจํ™”๋œ ์ปดํฌ๋„ŒํŠธ์— ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์— ํ•จ์ˆ˜๋ฅผ ์„ ์–ธํ•˜์—ฌ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก ํ•˜๊ฑฐ๋‚˜, useCallback์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋ Œ๋”๋ง ์‚ฌ์ด์— ํ•จ์ˆ˜์˜ ์„ ์–ธ์„ ์บ์‹œํ•ฉ๋‹ˆ๋‹ค.


์‚ฌ์šฉ์ž ์ •์˜ ๋น„๊ต ํ•จ์ˆ˜ ์ง€์ •ํ•˜๊ธฐ

๋“œ๋ฌผ์ง€๋งŒ ๋ฉ”๋ชจํ™”๋œ ์ปดํฌ๋„ŒํŠธ์˜ Props ๋ณ€๊ฒฝ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ์ •์˜ ๋น„๊ต ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜์—ฌ React๊ฐ€ ์–•์€ ๋น„๊ต๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ ์— ์ด์ „ Props์™€ ์ƒˆ๋กœ์šด Props๋ฅผ ๋น„๊ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” memo์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด Props๊ฐ€ ์ด์ „ Props์™€ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ true๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด false๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

const Chart = memo(function Chart({ dataPoints }) {
// ...
}, arePropsEqual);

function arePropsEqual(oldProps, newProps) {
return (
oldProps.dataPoints.length === newProps.dataPoints.length &&
oldProps.dataPoints.every((oldPoint, index) => {
const newPoint = newProps.dataPoints[index];
return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;
})
);
}

์ด ๊ฒฝ์šฐ ๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์˜ ์„ฑ๋Šฅ ํŒจ๋„์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๊ต ๊ธฐ๋Šฅ์ด ์‹ค์ œ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋น ๋ฅธ์ง€ ํ™•์ธํ•˜์„ธ์š”. ๋†€๋ž„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ฑ๋Šฅ ์ธก์ •์„ ํ•  ๋•Œ, React๊ฐ€ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

์ฃผ์˜ํ•˜์„ธ์š”!

arePropsEqual๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ ํ•จ์ˆ˜๋ฅผ ํฌํ•จํ•˜์—ฌ ๋ชจ๋“  Prop๋ฅผ ๋น„๊ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜๋Š” ์ข…์ข… ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ Props์™€ State๋ฅผ ํด๋กœ์ €Closure๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค. oldProps.onClick !== newProps.onClick์ผ ๋•Œ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ onClick ํ•ธ๋“ค๋Ÿฌ ๋‚ด์—์„œ ์ด์ „ ๋ Œ๋”๋ง์˜ Props์™€ State๋ฅผ ๊ณ„์† โ€œ์ธ์‹โ€ํ•˜์—ฌ ๋งค์šฐ ํ˜ผ๋ž€์Šค๋Ÿฌ์šด ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ž‘์—… ์ค‘์ธ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ์•Œ๋ ค์ง„ ์ œํ•œ๋œ ๊นŠ์ด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  100% ํ™•์‹ ํ•˜์ง€ ์•Š๋Š” ํ•œ, arePropsEqual ๋‚ด์—์„œ ๊นŠ์€ ๋น„๊ต๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ๋งˆ์„ธ์š”. ๊นŠ์€ ๋น„๊ต๋Š” ๋งค์šฐ ๋А๋ ค์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋‚˜์ค‘์— ๋ˆ„๊ตฐ๊ฐ€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ์•ฑ์ด ์ž ๊น ์ •์ง€๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


Do I still need React.memo if I use React Compiler?

When you enable React Compiler, you typically donโ€™t need React.memo anymore. The compiler automatically optimizes component re-rendering for you.

Hereโ€™s how it works:

Without React Compiler, you need React.memo to prevent unnecessary re-renders:

// Parent re-renders every second
function Parent() {
const [seconds, setSeconds] = useState(0);

useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(interval);
}, []);

return (
<>
<h1>Seconds: {seconds}</h1>
<ExpensiveChild name="John" />
</>
);
}

// Without memo, this re-renders every second even though props don't change
const ExpensiveChild = memo(function ExpensiveChild({ name }) {
console.log('ExpensiveChild rendered');
return <div>Hello, {name}!</div>;
});

With React Compiler enabled, the same optimization happens automatically:

// No memo needed - compiler prevents re-renders automatically
function ExpensiveChild({ name }) {
console.log('ExpensiveChild rendered');
return <div>Hello, {name}!</div>;
}

Hereโ€™s the key part of what the React Compiler generates:

function Parent() {
const $ = _c(7);
const [seconds, setSeconds] = useState(0);
// ... other code ...

let t3;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <ExpensiveChild name="John" />;
$[4] = t3;
} else {
t3 = $[4];
}
// ... return statement ...
}

Notice the highlighted lines: The compiler wraps <ExpensiveChild name="John" /> in a cache check. Since the name prop is always "John", this JSX is created once and reused on every parent re-render. This is exactly what React.memo does - it prevents the child from re-rendering when its props havenโ€™t changed.

The React Compiler automatically:

  1. Tracks that the name prop passed to ExpensiveChild hasnโ€™t changed
  2. Reuses the previously created JSX for <ExpensiveChild name="John" />
  3. Skips re-rendering ExpensiveChild entirely

This means you can safely remove React.memo from your components when using React Compiler. The compiler provides the same optimization automatically, making your code cleaner and easier to maintain.

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

The compilerโ€™s optimization is actually more comprehensive than React.memo. It also memoizes intermediate values and expensive computations within your components, similar to combining React.memo with useMemo throughout your component tree.


Troubleshooting

My component re-renders when a prop is an object, array, or function

React๋Š” ์–•์€ ๋น„๊ต๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ด์ „ Props์™€ ์ƒˆ๋กœ์šด Props๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๊ฐ๊ฐ์˜ ์ƒˆ๋กœ์šด Prop๊ฐ€ ์ด์ „ Prop์™€ ์ฐธ์กฐ๊ฐ€ ๋™์ผํ•œ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค. ๋ถ€๋ชจ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด์„ ์ƒ์„ฑํ•˜๋ฉด, ๊ฐœ๋ณ„ ์š”์†Œ๋“ค์ด ๋ชจ๋‘ ๋™์ผํ•˜๋”๋ผ๋„ React๋Š” ์—ฌ์ „ํžˆ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๋ฉด React๋Š” ํ•จ์ˆ˜์˜ ์ •์˜๊ฐ€ ๋™์ผํ•˜๋”๋ผ๋„ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ Props๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๊ฑฐ๋‚˜ ๋ฉ”๋ชจํ™” ํ•˜์„ธ์š”.