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

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

๋ ˆํผ๋Ÿฐ์Šค

memo(Component, arePropsEqual?)

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

import { memo } from 'react';

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

์•„๋ž˜์—์„œ ๋” ๋งŽ์€ ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

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

  • Component: memoize ํ•˜๋ ค๋Š” ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. memo๋Š” ์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ๋Œ€์‹  ์ƒˆ๋กœ์šด memoized ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜์™€ 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์™€ ๋™์ผํ•˜๋‹ค๋ฉด ๋ถ€๋ชจ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋  ๋•Œ ์ƒˆ๋กœ์šด props๊ฐ€ ์ด์ „ props์™€ ๋™์ผํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง ๋˜์ง€ ์•Š๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ memoized ์ƒํƒœ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ๋ฅผ memoize ํ•˜๋ ค๋ฉด 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>;
});

Note

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

Deep Dive

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

์ด ์‚ฌ์ดํŠธ์™€ ๊ฐ™์ด ๋Œ€๋ถ€๋ถ„์˜ ์ธํ„ฐ๋ž™์…˜์ด ํˆฌ๋ฐ•ํ•œ ์•ฑ์˜ ๊ฒฝ์šฐ(ํŽ˜์ด์ง€ ๋˜๋Š” ์ „์ฒด ์„น์…˜ ๊ต์ฒด ๋“ฑ) ์ผ๋ฐ˜์ ์œผ๋กœ memoization๋Š” ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด ์•ฑ์ด ๊ทธ๋ฆผ ํŽธ์ง‘๊ธฐ์ด๊ณ  ๋„ํ˜• ์ด๋™๊ณผ ๊ฐ™์ด ๋Œ€๋ถ€๋ถ„์˜ ์ธํ„ฐ๋ž™์…˜์ด ์„ธ๋ถ„๋˜์–ด ์žˆ๋‹ค๋ฉด, memoization๊ฐ€ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

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

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

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

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


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

์ปดํฌ๋„ŒํŠธ๊ฐ€ memoization ๋œ ๊ฒฝ์šฐ์—๋„, ์ปดํฌ๋„ŒํŠธ์˜ 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.Provider value={theme}>
      <button onClick={handleClick}>
        Switch theme
      </button>
      <Greeting name="Taylor" />
    </ThemeContext.Provider>
  );
}

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๋ฅผ ํด๋กœ์ €๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค. oldProps.onClick !== newProps.onClick์ผ ๋•Œ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ onClick ํ•ธ๋“ค๋Ÿฌ ๋‚ด์—์„œ ์ด์ „ ๋ Œ๋”๋ง์˜ props์™€ state๋ฅผ ๊ณ„์† โ€œ์ธ์‹โ€ํ•˜์—ฌ ๋งค์šฐ ํ˜ผ๋ž€์Šค๋Ÿฌ์šด ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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


๋ฌธ์ œ ํ•ด๊ฒฐ

prop๊ฐ€ ๊ฐ์ฒด, ๋ฐฐ์—ด ๋˜๋Š” ํ•จ์ˆ˜์ธ ๊ฒฝ์šฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.

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