스파르타코딩클럽 내일배움캠프 TIL67

한재창·2023년 2월 3일
0

memoization을 통한 최적화

  • memoization를 하기 위한 React의 방법들
  • 메모(기억)을 한다. 캐시 같은 곳에 저장해서 그때그때 찾아서 쓴다.
  • 불필요한 렌더링이 발생하지 않도록 하기 위해 사용한다.
  • React.memo, useCallback, usememo

리-렌더링의 발생 조건

  • 컴포넌트에서 State가 바뀌었을 때
  • 컴포넌트가 내려받은 Props가 변경되었을 때
  • 부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트는 모두

React.memo

  • 컴포넌트를 캐싱

  • 부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트는 모두 리-렌더링 된다.

  • 1번 컴포넌트가 리렌더링 된 경우 2~7번 모두 리렌더링 된다.

  • 2번 컴포넌트가 리렌더링 된 경우 4~7번 모두 리렌더링 된다.
    ❌ 문제 : 자녀 컴포넌트의 입장에서 바뀐게 없을 때 리렌더링 되는 것이 비효율적일 수 있다.
    💡 해결 : React.memo 를 사용한다.

  • 예제

    • 상위 컴포넌트에서 버튼을 누르면 count가 변한다.
    • 하위 컴포넌트는 바뀌는게 없지만 상위 컴포넌트가 리렌더링 되서 같이 리렌더링 된다.

// 상위 컴포넌트

import React, { useState } from "react";
import Box1 from "./Box1";
import Box2 from "./Box2";
import Box3 from "./Box3";

export default function Test() {
  console.log("테스트가 렌더링 되었습니다.");
  const [count, setCount] = useState(0);
  return (
    <div>
      <h3>카운트 예제</h3>
      <p>현재 카운트 : {count}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>+</button>
      <button onClick={() => setCount((prev) => prev - 1)}>-</button>

      <div style={{ display: "flex" }}>
        <Box1 />
        <Box2 />
        <Box3 />
      </div>
    </div>
  );
}
// 하위 컴포넌트

function Box1() {
  console.log("박스 1이 렌더링 되었습니다.");
  return <div style={boxStyle}>Box1</div>;
}

export default Box1;
  • 이 경우 하위 컴포넌트에서 React.memo를 사용하면 된다.
  • 하위 컴포넌트인 Box1 에서만 React.memo를 사용해 준 결과
// 하위 컴포넌트

function Box1() {
  console.log("박스 1이 렌더링 되었습니다.");
  return <div style={boxStyle}>Box1</div>;
}

export default React.memo(Box1);

useCallback

  • 함수를 캐싱
  • React.memo를 사용했는데도 불구하고 하위 컴포넌트가 리렌더링 된다.
// 상위 컴포넌트

export default function Test() {
  console.log("테스트가 렌더링 되었습니다.");
  const reset = () => setCount(0);

  const [count, setCount] = useState(0);
  return (
    <div>
      <div style={{ display: "flex" }}>
        <Box1 />
        <Box2 reset={reset} />
        <Box3 />
      </div>
    </div>
  );
}
// 하위 컴포넌트

function Box2({ reset }) {
  console.log("박스 2이 렌더링 되었습니다.");
  return (
    <div style={boxStyle}>
      <button onClick={reset}>초기화</button>
    </div>
  );
}

export default React.memo(Box2);
  • 컴포넌트에서 State가 바뀌었는가? No / 부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트는 모두 No => React.memo로 막아줌

  • 이 경우 Props가 변경되서 리렌더링 된 것이다.

    • function은 객체의 한 종류이다.
    • 자바스크립트가 object를 저장하는 방법 => object를 저장하는 것이 아니라 주소를 저장한다. 따라서 a,b는 주소값이 서로 다르기 때문에 다른 object이다.
    • Test가 렌더링이 일어나면 코드 순서대로 위에서부터 다시 그려지게 된다. 다시 그러지는 순간 Props로 내려준 reset라는 함수가 새로운 메모리에 다시 할당된다.
    • 하위 컴포넌트에서 Props를 받을 때 주소값이 다르기 때문에 다른 Props라고 생각, 즉 Props가 바뀌었다고 생각한다.
  • 예제

    • 이 현상을 방지하기 위해 useCallback를 사용해 함수를 기억하게 해서 쉽게 가져다 써야한다. => 이 함수 자체의 내용이 바뀌지 않기 때문에!
    • 상위 컴포넌트에서 Props로 내려주는 함수에 useCallback를 쓴 결과 하위 컴포넌트는 리렌더링 되지 않는다.
export default function Test() {
  console.log("테스트가 렌더링 되었습니다.");
  const reset = useCallback(() => setCount(0), []);

  const [count, setCount] = useState(0);
  return (
    <div>
      <div style={{ display: "flex" }}>
        <Box1 />
        <Box2 reset={reset} />
        <Box3 />
      </div>
    </div>
  );
}
  • 문제점
    • 카운트를 수정하고 함수를 호출하면 ${count} 는 최초 기억하는 값인 0이 뜬다.
 const reset = useCallback(() => {
    console.log(`Count가 ${count}에서 0으로 변경되었습니다.`);
    setCount(0);
  }, []);
  • 해결
    • 의존성 배열에 count 를 넣어 해결하였다.
    • 하지만 하위 컴포넌트인 Box2도 리렌더링 된다.
    • 즉 의존성 배열에 값을 넣을 필요가 없을 때 useCallback을 활용하면 좋다!
 const reset = useCallback(() => {
    console.log(`Count가 ${count}에서 0으로 변경되었습니다.`);
    setCount(0);
  }, [count]);

useMemo

  • 값을 캐싱 : 객체나 배열이나 함수가 이에 해당

  • 동일한 값을 반환하는 함수를 계속 호출하는 등 불필요한 렌더링을 할 때 사용한다.

  • 맨 처음 해당 값을 반환할 때 그 값을 특별한 곳(메모리)에 저장 => 필요할 때마다 다시 함수를 호출하는 것이 아니라 이미 저장한 값을 꺼내와서 쓴다.

  • 예시

    • 함수 리턴값 전 오래걸리는 일을 하더라도 리턴값은 100이다.
    • 처음에 렌더링 될 때를 제외하고는 컴포넌트가 렌더링 될 때 마다 함수가 리렌더링 될 필요가 없다.
    • return 값을 기억하기 위해 useMemo()를 쓴다.
    • 사용하기 전에는 렌더링 될 때마다 동일한 값을 반환하는 함수를 계속 호출하여 렌더링이 오래 걸렸으나 useMemo()를 사용하여 해결하였다.
function Box3() {
  const [count, setCount] = useState(0);
  
  const heavyWork = () => {
    for (let i = 0; i < 500000000; i++) {
      //
    }
    return 100;
  };
  
  // const value = heavyWork() => useMemo를 사용하지 않을 때
  const value = useMemo(() => heavyWork(), []); => useMemo를 사용할 때

  return (
    <div style={boxStyle}>
      {value}
      <br />
      <button onClick={() => setCount((prev) => prev + 1)}>+</button>
      <br />
      {count}
    </div>
  );
}

export default React.memo(Box3);

마치며

무조건 쓰면 좋으냐 ? No => 가벼운 하위 컴포넌트에 전부 다 써주면 캐시를 어마어마하게 잡아 먹기 때문에 오히려 안쓰는게 최적화에 더 효율적이다.
props에서 변화가 너무 많이 일어나면 오히려 안쓰는 것이 효율적이다.

번외 : div태그만 쓰면 seo에 안좋다. => 검색할 때 태그도 다 반영하기 때문에

profile
취준 개발자

0개의 댓글