Memoization

ν•™μŠ΅ ν‚€μ›Œλ“œ

  • memoization

    • React.memo

    • useCallback

    • useMemo

컴퓨터 ν”„λ‘œκ·Έλž¨μ΄ λ™μΌν•œ 계산을 λ°˜λ³΅ν•΄μ•Ό ν•  λ•Œ, 이전에 계산값을 λ©”λͺ¨λ¦¬μ— μ €μž₯ν•¨μœΌλ‘œμ¨ λ™μΌν•œ κ³„μ‚°μ˜ λ°˜λ³΅μˆ˜ν–‰μ„ μ œκ±°ν•˜μ—¬ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 속도λ₯Ό λΉ λ₯΄κ²Œ ν•˜λŠ” 기술

React.memo

  • μ»΄ν¬λ„ŒνŠΈλ₯Ό λ©”λͺ¨μ΄μ œμ΄μ…˜ ν•  λ•Œ μ‚¬μš©ν•œλ‹€.

  • μ»΄ν¬λ„ŒνŠΈμ˜ propκ°€ λ³€κ²½λ˜μ§€ μ•ŠλŠ” 경우 λ¦¬λ Œλ”λ§μ„ ν•˜μ§€ μ•ŠλŠ”λ‹€.

const MemoizedComponent = React.memo(SomeComponent, arePropsEqual?)

πŸ‘©πŸ»β€πŸ’» μ˜ˆμ‹œ

import { useState } from 'react';

const Count = ({ count }: { count: number }) => {
  console.log('Count Rendered');
  return (
    <div>
      <h1>Count Component</h1>
      <h2>Count : {count}</h2>
    </div>
  );
};

export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  return (
    <div>
      <Count count={count} />
      <button type="button" onClick={() => setCount(count + 1)}>
        count 증가 λ²„νŠΌ
      </button>
      <input
        type="text"
        placeholder="input μž…λ ₯ν•˜λ©΄ μ–΄λ–»κ²Œ 될까? "
        value={text}
        onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
          setText(e.target.value)
        }
      />
    </div>
  );
}

ν•΄λ‹Ή μ½”λ“œλŠ” input μ˜μ—­μ— μž…λ ₯ν•˜κ²Œ 되면 Count μ»΄ν¬λ„ŒνŠΈκ°€ λΆˆν•„μš”ν•˜κ²Œ λ¦¬λ Œλ”λ§μ΄ μΌμ–΄λ‚œλ‹€. Count μ»΄ν¬λ„ŒνŠΈμ˜ λ¦¬λ Œλ”λ§μ΄ μΌμ–΄λ‚˜λŠ” μ΄μœ λŠ” μƒμœ„ μ»΄ν¬λ„ŒνŠΈ App의 text의 μƒνƒœκ°’μ΄ μ—…λ°μ΄νŠΈ λ˜λ©΄μ„œ λ¦¬λ Œλ”λ§μ΄ μΌμ–΄λ‚˜κ²Œ λ˜λŠ” 것이닀.

import { useState, memo } from 'react';

const Count = ({ count }: { count: number }) => {
  console.log('Count Rendered');
  return (
    <div>
      <h1>Count Component</h1>
      <h2>Count : {count}</h2>
    </div>
  );
};

const MemoCount = memo(Count); // πŸ‘ˆπŸ» λ©”λͺ¨μ΄μ œμ΄μ…˜ν•œ μ»΄ν¬λ„ŒνŠΈ

export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  return (
    <div>
      <MemoCount count={count} />
      <button type="button" onClick={() => setCount(count + 1)}>
        count 증가 λ²„νŠΌ
      </button>
      <input
        type="text"
        placeholder="input μž…λ ₯ν•˜λ©΄ μ–΄λ–»κ²Œ 될까? "
        value={text}
        onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
          setText(e.target.value)
        }
      />
    </div>
  );
}

Count μ»΄ν¬λ„ŒνŠΈκ°€ λΆˆν•„μš”ν•˜κ²Œ λ¦¬λ Œλ”λ§μ„ 막기 μœ„ν•΄ React.memoλ₯Ό ν™œμš©ν•΄ μ»΄ν¬λ„ŒνŠΈλ₯Ό λ©”λͺ¨μ΄μ œμ΄μ…˜ ν–ˆμœΌλ―€λ‘œ propsκ°€ λ³€κ²½λ˜μ§€ μ•ŠλŠ” ν•œ Count μ»΄ν¬λ„ŒνŠΈλŠ” λ¦¬λ Œλ”λ§ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€.

useCallback

  • ν•¨μˆ˜λ₯Ό λ©”λͺ¨μ΄μ œμ΄μ…˜ ν•  λ•Œ μ‚¬μš©ν•œλ‹€.

  • λ¦¬λ Œλ”λ§ 간에 ν•¨μˆ˜μ˜ μ •μ˜λ₯Ό μΊμ‹±ν•΄μ£ΌλŠ” React Hook

const cachedFn = useCallback(() => {}, dependencies);

πŸ‘©πŸ»β€πŸ’» μ˜ˆμ‹œ

import { memo, useState } from 'react';

const Count = ({ count }: { count: number }) => {
  console.log('Count Rendered');
  return (
    <div>
      <h1>Count Component</h1>
      <h2>Count : {count}</h2>
    </div>
  );
};

const Button = ({ onClick }: { onClick: () => void }) => {
  console.log('Button Rendered');
  return (
    <button type="button" onClick={onClick}>
      count 증가 λ²„νŠΌ
    </button>
  );
};

const MemoCount = memo(Count);
const MemoButton = memo(Button);

export default function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  const onClickHandler = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <MemoCount count={count} />
      <MemoButton onClick={onClickHandler} />
      <input
        type="text"
        placeholder="input μž…λ ₯ν•˜λ©΄ μ–΄λ–»κ²Œ 될까? "
        value={text}
        onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
          setText(e.target.value)
        }
      />
    </div>
  );
}

ν•΄λ‹Ή μ½”λ“œλŠ” input μ˜μ—­μ— μž…λ ₯ν•˜κ²Œ 되면 μ»΄ν¬λ„ŒνŠΈκ°€ λΆˆν•„μš”ν•˜κ²Œ λ¦¬λ Œλ”λ§μ„ 막기 μœ„ν•΄ memoλ₯Ό μ‚¬μš©ν–ˆλ‹€. ν•˜μ§€λ§Œ, Button μ»΄ν¬λ„ŒνŠΈλŠ” μž…λ ₯μ‹œ 계속 λ¦¬λ Œλ”λ§μ΄ λ°œμƒλœλ‹€. κ·Έμ΄μœ λŠ” λ¬΄μ—‡μΌκΉŒ? Button μ»΄ν¬λ„ŒνŠΈλŠ” ν•¨μˆ˜λ₯Ό props둜 λ°›κ³  μžˆλ‹€. ν•¨μˆ˜λŠ” μ°Έμ‘°μžλ£Œν˜•μœΌλ‘œ λ©”λͺ¨λ¦¬μ˜ μ£Όμ†Œκ°’μ„ μ €μž₯ν•œλ‹€. 즉, μƒμœ„ μ»΄ν¬λ„ŒνŠΈ App의 λ¦¬λ Œλ”λ§ ν•˜κ²Œ 되면 onClickHandler ν•¨μˆ˜λŠ” 이전과 같은 ν•¨μˆ˜κ°€ μ•„λ‹Œ μƒˆλ‘œμš΄ ν•¨μˆ˜λ‘œ μƒμ„±λ˜μ–΄ Button μ»΄ν¬λ„ŒνŠΈκ°€ λ¦¬λ Œλ”λ§ 되게 λœλ‹€.

const onClickHandler = useCallback(() => {
  setCount(count + 1);
}, []);

onClickHandler ν•¨μˆ˜λ₯Ό useCallback을 μ‚¬μš©ν•΄ λ©”λͺ¨μ΄μ œμ΄μ…˜ ν•˜κ²Œ 되면 input μ˜μ—­μ— μž…λ ₯μ‹œ 더이상 λ¦¬λ Œλ”λ§μ΄ λ˜μ§€ μ•ŠλŠ”λ‹€.

🚨 μ˜ˆμƒμΉ˜ λͺ»ν•œ 이슈

λΆˆν•„μš”ν•œ λ¦¬λ Œλ”λ§μ€ μ—†μ§€λ§Œ, count 증가 λ²„νŠΌ ν΄λ¦­μ‹œ count 값이 1둜 ν•œλ²ˆλ§Œ μ—…λ°μ΄νŠΈλ˜κ³ , Count μ»΄ν¬λ„ŒνŠΈκ°€ 더이상 μ—…λ°μ΄νŠΈ λ˜μ§€ μ•ŠλŠ”λ‹€. κ·Έμ΄μœ λŠ” λ¬΄μ—‡μΌκΉŒ? useCallback의 μ˜μ‘΄μ„±λ°°μ—΄μ˜ 값은 λΉˆκ°’μ΄μ—¬μ„œ 초기 λ Œλ”λ§μ‹œμ—λ§Œ ν•΄λ‹Ή ν•¨μˆ˜λ₯Ό μƒμ„±ν•˜κ³  κ·Έ μ΄ν›„μ—λŠ” 더이상 ν•¨μˆ˜λ₯Ό μž¬μƒμ„±ν•˜μ§€ μ•ŠλŠ”λ‹€. μ΄λ•Œ useCallback 인자둜 전달받은 μ½œλ°±ν•¨μˆ˜μ—μ„œ count μ΄ˆκΈ°κ°’ 0을 μ°Έμ‘°ν•˜κ³  μžˆλ‹€. count 증가 λ²„νŠΌλ₯Ό ν΄λ¦­ν•˜λ©΄ count μƒνƒœκ°’μ€ 1둜 λ³€κ²½λ˜μ–΄ λ¦¬λ Œλ”λ§μ΄ λ°œμƒλœλ‹€. λ‹€μ‹œ ν•œλ²ˆ count 증가 λ²„νŠΌ ν΄λ¦­ν•˜λ©΄ useCallback 인자둜 전달받은 μ½œλ°±ν•¨μˆ˜μ˜ countλŠ” λ³€κ²½λœ μƒνƒœ 1이 μ•„λ‹Œ count μ΄ˆκΈ°κ°’ 0 μ°Έμ‘°ν•΄μ„œ κ³„μ‚°ν•΄μ„œ setCount(count + 1) λ™μž‘ν•˜λ©΄ 1이기 λ•Œλ¬Έμ— Count μ»΄ν¬λ„ŒνŠΈμ˜ propsλŠ” λ³€κ²½λ˜μ§€ μ•Šμ•˜κΈ°μ— λ¦¬λ Œλ”λ§ λ°œμƒν•˜μ§€ μ•Šκ³ , count μƒνƒœκ°’λ„ μ—…λ°μ΄νŠΈ λ˜μ§€ μ•ŠλŠ”λ‹€.

const onClickHandler = useCallback(() => {
  setCount((prev) => prev + 1); // πŸ‘ˆπŸ» μ΄μ „μ˜ μƒνƒœκ°’μ„ μ°Έμ‘°ν•˜λ„λ‘ λ³€κ²½
}, []);

초기 λ Œλ”λ§ μ‹œ count μ΄ˆκΈ°κ°’ μƒνƒœλ₯Ό μ°Έμ‘°ν•˜λŠ”κ²Œ μ•„λ‹ˆλΌ, μ΄μ „μ˜ count의 μƒνƒœκ°’μ„ μ°Έμ‘°ν•  수 μžˆλŠ” μ½œλ°±ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄μ„œ setCount ν•¨μˆ˜κ°€ μ—…λ°μ΄νŠΈ λ˜λ„λ‘ λ³€κ²½ν•΄μ•Ό Count μ»΄ν¬λ„ŒνŠΈλŠ” λ¦¬λ Œλ”λ§ λ°œμƒν•˜κ²Œ λœλ‹€.

useMemo

  • κ°’λ₯Ό λ©”λͺ¨μ΄μ œμ΄μ…˜ ν•  λ•Œ μ‚¬μš©ν•œλ‹€.

  • λ¦¬λ Œλ”λ§ 사이에 계산 κ²°κ³Όλ₯Ό 캐싱할 수 있게 ν•΄μ£ΌλŠ” React Hook

    • λΉ„μš©μ΄ 높은 둜직의 μž¬κ³„μ‚° μƒλž΅

const cachedValue = useMemo(calculateValue, dependencies);

πŸ‘©πŸ»β€πŸ’» μ˜ˆμ‹œ

import { useState } from 'react';

// 3000만개의 λ°°μ—΄ 데이터
const initialItems = new Array(29_999_999).fill(0).map((_, i) => {
  return {
    id: i,
    selected: i === 29_999_998,
  };
});

export default function App() {
  const [count, setCount] = useState(0);
  // 3000만개의 λ°°μ—΄ 데이터λ₯Ό λ Œλ”λ§ λ§ˆλ‹€ μž¬μƒμ„±ν•˜κ³  있음
  const [items] = useState(initialItems);
  const selectItems = items.find((item) => item.selected);
  return (
    <div>
      <h1>Count : {count}</h1>
      <button type="button" onClick={() => setCount((prev) => prev + 1)}>
        증가
      </button>
      <p>{selectItems?.id}</p>
    </div>
  );
}

ν•΄λ‹Ήμ½”λ“œλŠ” App μ»΄ν¬λ„ŒνŠΈκ°€ λ¦¬λ Œλ”λ§μ„ ν•  λ•Œ λ§ˆλ‹€ 3000만개의 λ°°μ—΄ 데이터λ₯Ό μž¬μƒμ„±ν•˜κ³  μžˆλ‹€. 증가 λ²„νŠΌμ„ ν΄λ¦­μ‹œ 3000만개의 λ°°μ—΄ 데이터λ₯Ό μž¬μƒμ„±ν•˜λ‹€λ³΄λ‹ˆ μ—°μ‚° λΉ„μš©μ΄ ν¬κΈ°λ•Œλ¬Έμ— count μƒνƒœ μ—…λ°μ΄νŠΈμ‹œ 화면에 μ—…λ°μ΄νŠΈ 값이 좜λ ₯λ˜μ§€ μ•Šκ³  μžˆλ‹€.

const selectItems = useMemo(() => items.find((item) => item.selected), []);

useMemoλ₯Ό 톡해 λ©”λͺ¨μ΄μ œμ΄μ…˜ ν•œ 값을 λ¦¬λ Œλ”λ§μ‹œ μž¬μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— ν™”λ©΄ μ—…λ°μ΄νŠΈκ°€ λΉ λ₯΄κ²Œ λ™μž‘ν•˜κ²Œ λ˜μ—ˆλ‹€.

Last updated