본문 바로가기
🌳Frontend/react

React.memo vs useMemo vs useEffect

by Bㅐ추 2023. 3. 24.
728x90
반응형

https://medium.com/hcleedev/web-%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80-react-memo-usememo-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-4324a237a039

을 보고 정리한 글입니다.


React 에는 memoization 이라는 개념을 활용한다.

memoization 이란 특정 함수가 동작하고 반환된 그 결과물을 기억하고 있는 것이다.

function sum(a, b) {
  return a + b;
}

위의 함수에 memoization 을 적용하면,

처음 누군가 sum(1, 2)를 호출했다면 sum을 a: 1, b: 2와 호출했고, 3이 반환되었다 라는 정보를 기록해둔다.
그러고 다시 반복되어서 sum(1, 2) 를 부르면 기록된 정보를 살피고, “sum 을 1과 2와 함께 불렀으면 3이 나오지!”라고 굳이 계산 과정을 거치지 않을 수 있다. 물론 다른 경우에 sum(2, 3) 이런 호출이 생기면 이 함수는 다시 돌아갈 것이다.

출처: https://medium.com/hcleedev/web-%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80-react-memo-usememo-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-4324a237a039

정리하자면, 어떠한 함수를 어떤 매개변수와 함께 불렀는지를 기억하는 것이라 보면 된다.


React.memo

어떤 컴포넌트를 어떤 props 와 함께 불렀는지 기록하고 기억하는 것.

function App() {
  const [state, setState] = useState<number>(0);
  return (
    <>
      <p>state</p>
      <button onClick={() => setState(state + 1)}>click</button>
      <MemoTest str={"안녕!"} />
    </>
  );
}
...
interface Props {
  str: string;
}

const MemoTest = ({str}: Props) => {
  return <div>{str}</div>;
};

state 가 변경되면 그 하위 컴포넌트인 MemoTest 도 리렌더링 된다.

 

이때, React.memo 로 감싸면 상황은 달라진다.

function App() {
  const [state, setState] = useState<number>(0);
  return (
    <>
      <p>state</p>
      <button onClick={() => setState(state + 1)}>click</button>
      <MemoTest str={"안녕!"} />
    </>
  );
}
...
interface Props {
  str: string;
}

const MemoTest = ({str}: Props) => {
  return <div>{str}</div>;
};
export default React.memo(MemoTest);

이경우, state 가 변경되더라도 MemoTest 는 리렌더링 되지 않는다. "str: 안녕" 이라는 값을 계속 props 로 받고있기 때문.

React.memo 는 같은 props 로 리렌더링하는 경우가 많을 때 사용한다.

 

function App() {
  const [state, setState] = useState<number>(0);
  const handleClick = () => console.log('click');
return (
    <>
      <p>state</p>
      <button onClick={() => setState(state + 1)}>click</button>
      <MemoTest handleClick={handleClick} />
    </>
  );
}
...
interface Props {
  handleClick: () => void;
}

const MemoTest = ({handleClick}: Props) => {
  return <div onClick={handleClick}}>눌러</div>;
};
export default React.memo(MemoTest);

하지만 위의경우 조심해야한다.

handleClick 은 원시타입이 아닌 함수타입이기 때문에, 리렌더링 될 때 마다 새로운 주소에 할당된다.

즉 MemoTest 가 해당 함수타입을 받게되면, 이전처럼 계속 리렌더링이 일어난다.


useMemo

복잡한 계산결과값을 memoization 할 수 있는 Hook 이다.

function App() {
  const items = useState([]);
  const convertedItems = items.map(item => { 
    ...item,
    additionalData: somethingHardCalc(item)
  }
  ...
}

App 이 리렌더링 될 때 마다 convertedItems 는 불필요한 계산을 계속 하게된다.

function App() {
  const items = useState([]);
  const convertedItems = useMemo(
    items.map(item => { 
      ...item,
      additionalData: somethingHardCalc(item)
    }, [items]
  )
  ...
}

단, useMemo 를 사용하게되면, item 이 바뀔때 만 계산을 하게된다.

useMemo 라는 함수와, 그 프로퍼티인 items 값이 뭐가 들어오는지 확인하고, 처음보는 값이면 첫 번째 인자의 함수를 구동해 그 값을 기록.
이전에 본 값이면 계산은 생략하고 전에 기록해둔 값을 돌려주는 방식으로 복잡한 계산을 줄일 수 있음.
  • React.memo의 경우에는 컴포넌트를 받아 컴포넌트를 반환
    • props 를 검사
  • useMemo의 경우에는 값을 계산하는 과정을 최적화해 값을 반환
    • dept 를 검사

useEffect

위의 useMemo 예시를 useEffect 로도 가능하다.

const [convertedItems, setConvertedItems] = useState([]);
useEffect(() => {
  ...계산과정...
  setConvertedItems(result);
}, [items]);

 

useMemo 의 경우에는 랜더되는 동안 계산

useEffect 의 경우에는 랜더된 후 함수를 호출해, 다시 setConvertedItems 를 부르고, 그로 인해 한 번의 리랜더링이 더 발생할 수 있다. 

 

즉 useMemo 는 렌더 중에, useEffect 는 렌더되고 나서 실행된다.

 

물론, useEffect 가 더 효과적인 경우도 있다.

 

useMemo 가 만약 오래 걸리는 작업을 많이 갖고 있다면, 랜더링 과정 중에 시간을 좀 오래 잡게 된다.

이때 사용자 입장에서는 화면 구성이 좀 느려진다고 생각할 수 있다.

하지만 useEffect 는 랜더링 후 돌아가므로 사용자 입장에서는 화면이 빨리 구성된다고 느낄 수는 있다.

 

비동기 작업 또한 마찬가지다.

useMemo 는 랜더링 작업 중에 돌아가기 때문에, 그때 비동기 작업의 결과값이 적절히 반영되는 것을 기대하는 것은 힘들다.

 

비동기 작업의 결과값은 side effect로서 컴포넌트에 영향을 미친다. 렌더링이 된 후 컴포넌트를 조작하다가 생기는 side effect 들은 useEffect 에서 관리할 일이라고 한다.

 

 

728x90
반응형