[React Hooks 총정리] Reat.memo로 컴포넌트 최적화하기

혜빈·2024년 7월 15일
0

REACT 보충개념

목록 보기
35/48
  • React는 성능이 좋기 때문에 컴포넌트의 렌더링 횟수에 대해 크게 고민할 필요는 없음
  • 하지만 렌더링이 너무 자주 발생해서 성능에 부담을 준다면 불필요한 렌더링은 최대한 막는게 좋음
  • 이름, 나이 , 주소를 props로 전달받아 다른 학생의 정보를 보여주는 컴포넌트
const Student = ({ name, age, address }) => {
	return (
    	<div>
        	<h1>{ name }</h1>
            <span>{ age }</span>
            <span>{ address }</span>
        </div>
    )
}
  • Student 컴포넌트를 자식으로 가지고 있는 부모 컴포넌트
  • 부모 컴포넌트가 렌더링 되면 모든 자식 컴포넌트도 렌더링 됨
  • 효율성을 높이기 위해 자식 컴포넌트가 꼭 필요할때만 렌더링되도록 설정할 필요가 있음
    - Student 컴포넌트가 받는 porps(name, age, address)가 변경이 될 때만 렌더링이 되도록 하려면? --> React.memo를 사용하자!
const School = (props) => {
	
    return (
    	<Student
        	name={"햅피빈"}
            age={28}
            address={"우리집"}
    	/>
    );
};

React.memo

  • React에서 제공하는 고차 컴포넌트 Higher Order Component(HOC)
    : 어떤 컴포넌트를 인자로 받아서 새로운 컴포넌트를 반환해주는 함수

  • React.memo에 어떤 컴포넌트를 넣어주면 UI적으로, 기능 적으로는 같지만 조금 더 최적화된 컴포넌트를 반환해줌

  • 최적화된 컴포넌트는 렌더링이 되어야 할 상황에 놓일때마다 Prop Check를 통해서 자신이 받는 Props에 변화가 있는지 없는지 확인함

  • 확인 후 Props의 변화가 있다면 -> 렌더링

  • 확인 후 Props의 변화가 없다면 -> 기존에 렌더링 된 내용을 재사용

  • React.memo의 memo는 Memoization을 의미

  • Memoization = 이미 계산해놓은 값을 메모리 상에 저장해놓고 필요할때마다 꺼내서 재사용하는 것


React.memo를 사용하기 적합한 상황

1) 컴포넌트가 같은 Props로 자주 렌더링 될 때
2) 컴포넌트가 렌더링 될때마다 복잡한 로직을 처리해야할 때


React.memo 주의사항

  • 오직 Props의 변화에만 의존하여 렌더링 될지 안될지 판단하는 방법임
  • 만약 useState, useReducer, useContext와 같은 상태와 관련된 Hook을 사용한다면 Props에 변화가 없더라도 State나 Context가 변할때마다 다시 렌더링이 된다는 사실 기억하기

실제 코드 구현

ReactMemo.js

import React, { useState } from "react";
import Child from "../components/Child";

function ReactMemo() {
  const [parentAge, setParentAge] = useState(0);
  const [childAge, setChildAge] = useState(0);

  const incrementParentAge = () => {
    setParentAge(parentAge + 1);
  };

  // 자식 Age를 1씩 증가시켜주는 함수
  const incrementChildAge = () => {
    setChildAge(childAge + 1);
  };

  console.log("부모 컴포넌트가 렌더링 되었습니다");

  return (
    <div style={{ border: "2px solid navy", padding: "10px" }}>
      <h1>부모</h1>
      <p>age: {parentAge}</p>
      <button onClick={incrementParentAge}>부모 나이 증가</button>
      <button onClick={incrementChildAge}> 자녀 나이 증가</button>
      <Child name={"홍길동"} age={childAge} />
    </div>
  );
}

export default ReactMemo;

Child.js

import React, { useState } from "react";
import Child from "../components/Child";

function ReactMemo() {
  const [parentAge, setParentAge] = useState(0);
  const [childAge, setChildAge] = useState(0);

  const incrementParentAge = () => {
    setParentAge(parentAge + 1);
  };

  // 자식 Age를 1씩 증가시켜주는 함수
  const incrementChildAge = () => {
    setChildAge(childAge + 1);
  };

  console.log("부모 컴포넌트가 렌더링 되었습니다");

  return (
    <div style={{ border: "2px solid navy", padding: "10px" }}>
      <h1>부모</h1>
      <p>age: {parentAge}</p>
      <button onClick={incrementParentAge}>부모 나이 증가</button>
      <button onClick={incrementChildAge}> 자녀 나이 증가</button>
      <Child name={"홍길동"} age={childAge} />
    </div>
  );
}

export default ReactMemo;

  • 그런데 부모 나이 증가 버튼을 누르면 부모의 나이만 증가하는데 콘솔에는 '자녀도 렌더링 되었습니다'라고 뜸

  • 자녀 나이 증가 버튼을 눌러도 마찬가지로 자녀의 나이만 증가하는데 콘솔에는 '부모 컴포넌트가 렌더링 되었습니다'라고 뜸

  • React 컴포넌트는 자신의 State가 업데이트 될 때마다 다시 렌더링 되기 때문에 ReactMemo 컴포넌트의 State인 parentAge와 childAge가 업데이트 될 때마다 ReactMemo 컴포넌트가 다시 렌더링 됨

  • 또한 컴포넌트가 가진 자식 컴포넌트도 다시 렌더링 됨
    (여기에서는 Child 컴포넌트가 해당 됨)

  • 따라서 '부모 나이 증가 버튼' 또는 '자녀 나이 증가 버튼'을 클릭하면 parentAge 또는 childAge State가 업데이트 되면서 ReactMemo 컴포넌트가 렌더링 되면서 위와 같은 내용이 콘솔에 출력이 됨

  • 그런데 이렇게 불필요하게 자녀 컴포넌트까지 모두 렌더링이 된다면 비효율적임 -> 해결방법은? React.memo를 사용하자!


React.memo 적용, 사용방법

  • Child 컴포넌트는 props로 받는 name과 age가 변경되지 않는 한 굳이 렌더링 될 필요가 없음
  • React.memo를 통해서 Child 컴포넌트의 props가 업데이트 되지 않았다면 렌더링을 막아주는 최적화를 시켜줄 수 있음

React.memo 사용방법

  1. memo를 import 시켜주기

  1. 최적화 하고 싶은 컴포넌트를 memo라는 함수로 감싸주기
    (여기에서는 Child 컴포넌트에 해당)

Child 컴포넌트의 전체 코드


  • 이렇게 하면 '부모 나이 증가 버튼'을 클릭하면 '부모 컴포넌트가 렌더링 되었습니다'만 콘솔에 출력 됨


React.memo의 동작방법

  • React.memo는 React에서 제공하는 고차 컴포넌트 중 하나임

  • 고차 컴포넌트는 하나의 함수임

  • 함수는 컴포넌트를 인자를 받아서 또 다른 컴포넌트를 반환해줌

  • React.memo는 우리가 넣어준 Child 컴포넌트를 인자로 받아서 최적화된 Child 컴포넌트를 반환해줌

  • React.memo를 통해 최적화가 된 컴포넌트는 렌더링이 될 상황에 놓일때마다 prop check라는 것을 함

  • prop check는 컴포넌트가 받는 props에 변화가 있을때만 렌더링을 허락해주고, 만약 props의 변화가 없다면 렌더링을 하지 않고 이전에 이미 렌더링된 컴포넌트의 결과를 다시 재사용 함


useMemo와 React.memo 함께 사용하기

  • 기본 코드

ReactMemo.js

import React, { useState } from "react";
import Child from "../components/Child";

function ReactMemo() {
  const [parentAge, setParentAge] = useState(0);
  const [childAge, setChildAge] = useState(0);

  const incrementParentAge = () => {
    setParentAge(parentAge + 1);
  };

  console.log("부모 컴포넌트가 렌더링 되었습니다");

  const name = {
    lastName: "홍",
    firstName: "길동",
  };

  return (
    <div style={{ border: "2px solid navy", padding: "10px" }}>
      <h1>부모</h1>
      <p>age: {parentAge}</p>
      <button onClick={incrementParentAge}>부모 나이 증가</button>
      <Child name={name} />
    </div>
  );
}

export default ReactMemo;

Child.js

import React, { memo } from "react";

const Child = ({ name }) => {
  console.log("자녀도 렌더링 되었습니다");

  return (
    <div style={{ border: "2px solid pink", padding: "10px" }}>
      <h3>자녀</h3>
      <p>name: {name.lastName}</p>
      <p>name: {name.firstName}</p>
    </div>
  );
};

export default memo(Child);
  • 기본 화면

  • Child 컴포넌트는 React.memo를 통해 최적화가 되어있기 때문에 props로 받는 name이 변경되는 경우가 아니면 렌더링 되지 않아야 함
  • 하지만 '부모 나이 증가 버튼'을 클릭하면 부모 컴포넌트와 자녀 컴포넌트가 모두 렌더링 되었다고 콘솔에 출력됨

  • 이유는? -> name이 object(객체)형태이기 때문임

  • JavaScript에서의 object는 string, number타입과 같은 원시 타입과 다르게 변수 안에 그대로 저장되는게 아니라,
    object가 저장되어있는 메모리의 주소가 여기에 저장되어 있음

  • ReactMemo 함수가 호출이 되면 함수 안의 모든 변수들이 다시 초기화가 되기 때문에 name도 초기화가 됨
    -> ReactMemo 컴포넌트가 렌더링 될때마다 계속해서 새로운 오브젝트가 만들어짐
    -> 만들어진 object는 각각 다른 메모리 주소에 저장이 됨
    -> name이라는 props에 변화가 있다고 받아들이기 ㅐ문에 Child 컴포넌트도 렌더링 됨

- 해결방법 :
메모리 주소가 변하지 않도록
useMemo Hook을 사용해서 object를 Memoization 해주자!

  • '부모 나이 증가 버튼' 클릭시 부모 컴포넌트만 렌더링 됨

- 결론 : useMemo와 React.memo 함께 사용하면 props으로 전달받는 값이 객체여도 Child 컴포넌트의 렌더링을 막아줄 수 있음


useCallback과 React.memo 함께 사용하기

  • 기본 코드

ReactMemo.js

import React, { useState } from "react";
import Child from "../components/Child";

function ReactMemo() {
  const [parentAge, setParentAge] = useState(0);

  const incrementParentAge = () => {
    setParentAge(parentAge + 1);
  };

  console.log("부모 컴포넌트가 렌더링 되었습니다");

  const tellMe = () => {
    console.log("길동아 사랑해 💕");
  };

  return (
    <div style={{ border: "2px solid navy", padding: "10px" }}>
      <h1>부모</h1>
      <p>age: {parentAge}</p>
      <button onClick={incrementParentAge}>부모 나이 증가</button>
      <Child name={"홍길동"} tellMe={tellMe} />
    </div>
  );
}

export default ReactMemo;

Child.js

import React, { memo } from "react";

const Child = ({ name, tellMe }) => {
  console.log("자녀도 렌더링 되었습니다");

  return (
    <div style={{ border: "2px solid pink", padding: "10px" }}>
      <h3>자녀</h3>
      <p>이름: {name}</p>
      <button onClick={tellMe}>엄마 나 사랑해?</button>
    </div>
  );
};

export default memo(Child);

  • 기본 화면

  • '엄마 나 사랑해?' 버튼 클릭시 콘솔에 '길동아 사랑해'가 출력됨

  • '부모 나이 증가' 클릭시 콘솔에 부모 컴포넌트와 자식 컴포넌트 모두 렌더링 되었다고 출력됨

  • Child가 받는 name, tellMe props가 업데이트 되지 않았고 Child는 React.memo로 최적화가 되어있음에도 불구하고 렌더링이 됨

  • 이유는? -> tellMe라는 prop이 함수를 전달받고 있기 때문임

  • JavaScrip에서 함수는 객체의 한 종류임

  • tellMe라는 변수 안에는 함수 객체가 들어있는 메모리 주소가 할당 되기 때문에 컴포넌트가 렌더링 될때마다 Child 컴포넌트의 TellMe prop으로 계속해서 다른 주소가 전달 됨

  • React.memo 입장에서는 메모리 주소가 변경되었으니 Child 컴포넌트를 다시 렌더링 시킴

- 해결방법: useCallback Hook을 사용하자!

  • useCallback은 useMemo와 비슷한 기능을 함
  • useMemo -> 어떠한 을 Memoization하기 위해 사용
  • useCallback은 어떠한 함수를 Memoization 하기 위해 사용함

  • '부모 나이 증가' 클릭시 부모 컴포넌트만 렌더링 됨

- 결론 : useCallback과 React.memo 함께 사용하면 Child prop으로 함수를 전달해줘도 Child 컴포넌트의 렌더링을 제한해줄 수 있음


주의할점

- React.memo는 꼭 필요할때만 사용해야 함

  • React.memo를 잘 사용한다면 불필요한 렌더링 횟수를 감소시킬 수 있기 때문에 이득을 볼 수 있지만, 컴포넌트를 Memoization 하여 재사용하기 위해서는 메모리를 사용해야하기 때문에 무분별하게 사용하면 안됨

- React.mmo는 오직 Pops 변화에만 의존하는 최적화 방법임

profile
최강 개발자를 꿈꾸는 병아리

0개의 댓글