코딩.zip

[React] 재렌더링 구조 & 메모이제이션 활용 최적화 본문

프로그래밍/React

[React] 재렌더링 구조 & 메모이제이션 활용 최적화

김주짱 2024. 3. 20. 14:49

 🐾 재렌더링 발생 조건

  • state가 업데이트 된 컴포넌트
  • props가 변경된 컴포넌트
  • 재렌더링 된 컴포넌트 아래의 모든 컴포넌트

☻ 사전준비

App 컴포넌트와 Child(n)의 관계

실습 구조

src
├── App.js
├── components
│   ├── Child1.jsx
│   ├── Child2.jsx
│   ├── Child3.jsx
│   └── Child4.jsx
├── index.css
├── index.js

 

🔔 메모이제이션(memoization)

- 함수의 반환 값을 저장해 두었다가 동일한 인수(argument)가 전달될 때, 이전에 계산한 값을 재사용하는 최적화,고속화 기법


✔︎ 컴포넌트의 메모이제이션 - memo

 : 컴포넌트의 props가 바뀌지 않았다면, 리렌더링하지 않도록 설정하는 함수 

  • App 컴포넌트가 재렌더링될 때, memo를 사용하여 감싼 경우, 이전에 렌더링된 결과를 재사용
  • 이전에 같은 인수로 렌더링된 결과가 이미 캐시되어 있기 때문에 새로 렌더링하지 않고 이전에 계산된 결과를 사용
  • 변경된 상태에 의존하지 않는 하위 컴포넌트를 새로 렌더링하지 않음 ➡️ Child4
  • 컴포넌트 내부에서 useState같은 훅을 사용할 때 상태가 변경 되면 리렌더링

✔︎ 변수의 메모이제이션 - useMemo

 : 첫번째 인수의 함수에 변수에 설정할 값의 반환 두번째 인수에 의존배열 전달


<Child1.jsx>

import { Child2 } from './Child2';
import { Child3 } from './Child3';
import { memo } from 'react';

const style = {
height : '200px',
backgroundColor : 'lightblue',
padding : '8px'
};

export const Child1 = memo((props) => {
    console.log('Child1 렌더링');

    // props로부터 함수를 전개(분할대입)
    const {onClickReset} = props;

    return (
        <div style={style}>
            <p>Child1</p>
            {/* 전달된 함수를 실행하는 버튼 설정 */}
            <button onClick={onClickReset}>리셋</button>
            <Child2 />
            <Child3 />
        </div>
    );
});

- const {onClickReset} = props; ➡️ App 컴포넌트의 onClickReset 함수를 Child1 컴포넌트의 props를 통해 받아온다.

 

 

문제 : 함수 정의도 props가 변경되지 않았는데 재렌더링 된다.

원인 : 함수를 props로 전달할 때 컴포넌트의 메모이제이션해도 재렌더링 됐던 이유는 - React에서는 함수가 새로 생성될 때마다 해당 함수를 사용하는 컴포넌트가 재렌더링되기 때문이다.

해결 :  함수 메모이제이션 - useCallback 사용한다.

 

✔︎  함수의 메모이제이션 - useCallback

 : 만들어놨던 함수 재활용을 통해 해당 함수의 참조가 변경되지 않음

 

<App.jsx>

import { useState, memo, useCallback } from "react";
import { Child1 } from "./components/Child1.jsx";
import { Child4 } from './components/Child4.jsx';
// import './App.css';

const App = memo(() => {
  console.log("렌더링 함수")
  
  const [num, setNum] = useState(0);

  const onClickButton = () => {
    setNum(num + 1);
    };

  /* 리셋 버튼을 누르면 Child1도 재렌더링
    const onClickReset = () => {
    setNum(0);
  };
  */

  // useCallback : Child1 재렌더링 x
  const onClickReset = useCallback(() => {
    setNum(0);
  },[]);


  return (
    <>
      <button onClick={onClickButton}>버튼</button>
      <p>{num}</p>
      {/* Child1 클릭하면 0으로 되돌리는 리셋버튼 구현 */}
      {/* state는 App.js가 가지고 있으므로 app안에서 리셋하기 위한 함수 정의하고 함수를 child에 전달 */}
      <Child1 onClickReset={onClickReset} /> 
      <Child4 />
    </>
  );
});


export default App; // export를 해줘야 index.jsx에서 App.jsx를 받을 수 있음

 

 

onClickReset 함수를 useCallback으로 감싸는 이유 (?)

 

재렌더링 최적화: onClickReset 함수가 App 컴포넌트 내부에서 상태 변경에 영향을 받지 않고 고정된 역할만 수행하는 경우, 해당 함수를 메모이제이션하여 불필요한 재생성을 방지할 수 있다.

의존성 배열 제어: useCallback의 두 번째 매개변수([]=빈배열)인 의존성 배열을 사용하여 함수가 의존하는 변수가 변경될 때만 함수가 재생성되도록 제어하는 것이다. 빈배열의 경우 해당 함수가 생성될 때 상태만 기억하고 상태가 변경되어도 함수를 재생성하지 않는다! 이를 통해 필요한 경우에만 함수를 재생성할 수 있고 그렇지 않으면 이전에 메모이제이션된 함수를 사용할 수 있다.

 

만약, useCallback의 두 번째 매개변수를 [num]으로 받았다면 (?)

 

onClickReset 함수는 num 상태에 의존하게 되고 이 경우, num 상태가 변경될 때마다 onClickReset 함수가 새로 생성된다.

그러므로 Child1 컴포넌트의 재렌더링이 발생할 때마다 onClickReset 함수가 다시 생성되고, Child1 컴포넌트의 렌더링 로그가 콘솔창에 찍히게 된다.

 

 


번외인데요, npm start 했을 때

자꾸 타입에러가 나더라구요... 미치고 환장할 노릇... 뭐 때문인데 :(

import useState from "react"; (X)

알고보니 useState가 react 패키지의 기본 함수가 아니기 때문에 디폴트 임포트를 하면 안됐던 것이다...  반드시 네임드 임포트 해주세요

 

📌 출처

https://stackoverflow.com/questions/68098641/typeerror-react-webpack-imported-module-0-default-is-not-a-function-or-its-r

 

TypeError: react__WEBPACK_IMPORTED_MODULE_0___default is not a function or its return value is not iterable

I'm simply following this tutorial on youtube. useState is giving errors. Please help me here import React from 'react' //import useState from 'react-dom'; import useState from 'react'; export...

stackoverflow.com