2. React Hooks

이준구·2024년 1월 31일
0

React

목록 보기
6/13
post-thumbnail

최적화(Optimization) 방법


리-렌더링의 발생 조건

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

최적화(Optimization) 방법의 종류

  • memo(React.memo) : 컴포넌트를 캐싱
  • useCallback : 함수를 캐싱
  • useMemo : 값을 캐싱


    캐싱(Caching)은 데이터나 연산 결과를 임시로 저장하는 메모리나 저장소

1. React.memo

- 부모 컴포넌트가 리렌더링될 때 모든 자식컴포넌트의 리렌더링을 방지하는 방법

주의사항
부모 컴포넌트의 state의 변경으로 인해 props가 변경이 일어나면 컴포넌트는 리렌더링된다.


**선언 방법 1) import { memo } from 'react' 2) React.memo**
import React, { useState } from "react";
import Box1 from "./components/Box1";
import Box2 from "./components/Box2";
import Box3 from "./components/Box3";

const boxesStyle = {
  display: "flex",
  marginTop: "10px",
};

function App() {
  console.log("App 컴포넌트가 렌더링되었습니다!");

  const [count, setCount] = useState(0);

  // 1을 증가시키는 함수
  const onPlusButtonClickHandler = () => {
    setCount(count + 1);
  };

  // 1을 감소시키는 함수
  const onMinusButtonClickHandler = () => {
    setCount(count - 1);
  };

  return (
    <>
      <h3>카운트 예제입니다!</h3>
      <p>현재 카운트 : {count}</p>
      <button onClick={onPlusButtonClickHandler}>+</button>
      <button onClick={onMinusButtonClickHandler}>-</button>
      <div style={boxesStyle}>
        <Box1 />
        <Box2 />
        <Box3 />
      </div>
    </>
  );
}

export default App;

export default React.memo(Box1);
export default React.memo(Box2);
export default React.memo(Box3);

// 각각의 컴포넌트 export 부분에 React.memo()를 선언 시
// 부모 컴포넌트(App.jsx)에서 state 변화(버튼 클릭)로 리-렌더링 되더라도 하위 컴포넌트들은 리-렌더링되지 않는다.




2. useCallback

-인자로 들어오는 함수 자체를 기억(memoization)한다.


1. useCallback 사용하지 않을 때

function App() {
 const [count, setCount] = useState(0);

    //plus
 const onPlusClickEventHandler = (e) => {
        setCount((prev) => prev +1)

    }
    //minus
 const onMinusClickEventHandler = (e) => {
       setCount((prev) => prev -1);
    }

 // count를 초기화해주는 함수
  const initCount = () => {
    setCount(0);
  };

 console.log("App이 렌더링 되었습니다.")  

  return (
    <>
      <h3>카운트 예제입니다!</h3>
      <p>현재 카운트 : {count}</p>
      <button onClick={onPlusButtonClickHandler}>+</button>
      <button onClick={onMinusButtonClickHandler}>-</button>
      <div style={boxesStyle}>
        
        //Box1에 props로 initCount 전달
        <Box1 initCount={initCount} />
        <Box2 />
        <Box3 />
      </div>
    </>
  );
 }


function Box1({ initCount }) {
  console.log("Box1이 렌더링되었습니다.");

  const onInitButtonClickHandler = () => {
    initCount();
  };

  return (
    <div style={boxStyle}>
      <button onClick={onInitButtonClickHandler}>초기화</button>
    </div>
  );
  
  export default React.memo(Box1);
  
  React.memo를 통해서 Box1.jsx는 메모이제이션을 했는데도 리-렌더링이 되는 이유
  - 전달 받은 props의 상태변화가 이루어져 리-렌더링된 것이다.
  - props의 상태변화가 이루어진 이유: App.jsx라는 함수형 컴포넌트를 사용하기 때문이다.
  - App.jsx라는 함수형 컴포넌트가 리렌더링 되면서 안에 있는 모든 코드를 재시작하기 때문이다.

}

문제점)
+ 버튼이나, - 버튼을 누를 때 그리고 초기화 버튼을 누를 때 모두
App 컴포넌트와 Box1 컴포넌트가 리-렌더링 되는 것을 볼 수 있습니다.

이유)
App 함수형 컴포넌트가 리-렌더링될 때마다 initCount 함수도 같이 초기화된다. 그로 인해 props의 상태 변화가 이루어져 Box1에서도 같이 리-렌더링 되는 걸 볼 수 있다.

해결)
initCount 함수에 useCallbak을 사용한다(인자로 들어오는 함수 자체를 기억하게 한다.)


2. useCallback을 사용할 때

//initCount 함수에 useCallback 선언
const initCount = useCallback(() => {
   setCount(0);
  
  console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`); 
}, []);  
//이 함수의 렌더링은 1번만 실행 후 리-렌더링되지 않는 걸 볼 수 있다. 
//증가, 감소, 초기화를 눌러도 함수가 리-렌더링되지 않는 걸 볼 수 있다.

추가적으로 알아두기

//initCount 함수에 useCallback 선언
const initCount = useCallback(() => {
   setCount(0);
  
  console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`); 
}, []); 

//문제점: 위의 console.log의 count값이 변하지 않고 0으로 고정된 걸 볼 수 있다.
//이유: 함수가 저장된 시점이 처음 렌더링될 시점의 값인 count 0을 메모리에 저장했기 때문이다. 
//그 이후 리-렌더링이 되지 않아 값이 고정인 상태이다.

//해결)
//의존성 배열을 사용하여 특정 배열의 값이 변할 경우 렌더링되게 만들면 된다.
//현재는 []배열에서 ==> [count]로 변경하면 된다.


3. useMemo

: 동일한 값을 반환하는 함수(반복문)의 처음 해당 값을 메모리에 임시 저장한다.

1. useMemo 예제

  const heavyWork = () => {
  for(let i=0; i <1000000000; i++){};
  return 100;
 }
  
 const value = useMemo(() => {
  heavyWork()
  },[]);
  
 console.log(value);
  
 //해석)
 // dependency Array의 값이 변경될 때만 `반환될 함수(heavyWork)`가 호출됩니다.
 // 그 외의 경우에는 memoization 해놨던 값을 가져온다.
  
 // 현재 dependency Array의 값은 []로, heavytWork 함수는 첫 렌더링 이후 리-렌더링이 되지 않고 있다.
 // 이후에 사용되는 value는 useMemo를 통해 memoization된 값을 사용하는 것이다.

2. useMemo 예제

function ObjectComponent() {
  const [isAlive, setIsAlive] = useState(true);
  const [uselessCount, setUselessCount] = useState(0);

  const me = {
    name: "Ted Chang",
    age: 21,
    isAlive: isAlive ? "생존" : "사망",
  };
  
  
  // 의존성 배열 위치에 me의 값을 넣어 변경될 경우에만 렌더링 되게끔 만들었지만
  // count를 증가하는 button을 눌러보면 계속 log가 찍히는 것을 볼 수가 있다.
  useEffect(() => {
    console.log("생존여부가 바뀔 때만 호출해주세요!");
  }, [me]);
  
  // 이유) count의 상태가 변하면서 현재 함수형 컴포넌트가 리-렌더링되어 me의 값 또한 초기화되어 상태가 변경된다
  // 그리하여 useEffect가 실행되는 걸 볼 수 있다.
  // 해결) me 객체인 값을 useMemo 해줘야한다.(아래처럼)
  //     그러면 count 증가 버튼을 눌러도 me 객체의 값이 리-렌더링되지 않아 
  //     useEffect부분이 실행되지 않는 걸 볼 수 있다.
  
  //   const me = useMemo(() => {
  //    return {name: "Ted Chang",
  //     age: 21,
  //     isAlive: isAlive ? "생존" : "사망",  
  //     }
  //   },[isAlive]); 
  
  
  return (
    <>
      <div>
        내 이름은 {me.name}이구, 나이는 {me.age}!
      </div>
      <br />
      <div>
        <button
          onClick={() => {
            setIsAlive(!isAlive);
          }}
        >
          누르면 살았다가 죽었다가 해요
        </button>
        <br />
        생존여부 : {me.isAlive}
      </div>
      <hr />
      필요없는 숫자 영역이에요!
      <br />
      {uselessCount}
      <br />
      <button
        onClick={() => {
          setUselessCount(uselessCount + 1);
        }}
      >
        누르면 숫자가 올라가요
      </button>
    </>
  );
}

export default ObjectComponent;

주의사항
useMemo를 남발하게 되면 별도의 메모리 확보를 너무나 많이 하게 되기 때문에 오히려 성능이 악화될 수 있습니다.

profile
개발 중~~~ 내 자신도 발전 중😂🤣

0개의 댓글

관련 채용 정보