React) React.memo

2ast·2022년 9월 26일
1

React

목록 보기
3/3

React.memo

React.memo는 react 프로젝트의 rendering 최적화를 위해 사용된다. React.memo는 react 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환해주는데, 이렇게 반환된 컴포넌트는 렌더링을 기점으로 props를 검사하여 이전의 props와 달라진 점이 있다면 컴포넌트를 리렌더링해주고, 만약 달라진 점이 없다면 새롭게 렌더링하지 않는다.

Basic

import React from 'react'

const CommonComponent = ({user}) =>{
	console.log("Running CommonComponet")

	return <div>{user.name}</div>
}

export default CommonComponent
import CommonComponent from './CommonComponent'
import {useState} from 'react'

const App = () =>{
	console.log("Running App")

	const [user,setUser] = useState("meta")
	const [toggleState, setToggleState] = useState(false)
	const onAppleClick = () => {
      setUser("apple");
      console.log("change to apple");
    };
    const onMetaClick = () => {
      setUser("meta");
      console.log("change to meta");
    };
    const onRerenderClick = () => {
      setToggleState(!toggleState);
      console.log("re-render!");
    };

  	return (
      <>
        <MemoizationComponent user={user} />
        <button onClick={onAppleClick}>Apple</button>
        <button onClick={onMetaClick}>Meta</button>
        <button onClick={onRerenderClick}>re-render</button>
      </>
    );
};

export default App

위 예시에서 re-render 버튼을 눌러 리렌더링을 유발하면, App component가 리렌더링 되며 자연스럽게 child component인 CommonComponent도 리렌더링 되는 것을 확인할 수 있다.

CommonComponent의 경우 toggleState와 아무런 관련이 없기 때문에 매번 CommonComponent가 같이 리렌더링 되는 것은 비효율적이라고 볼 수 있다. 바로 이러한 컴포넌트의 불필요한 렌더링을 방지하기 위해 React.memo가 사용된다.

import React from 'react'

const MemoizationComponent = ({user}) =>{
	console.log("Running MemoizationComponet")

	return <div>{user.name}</div>

}

export default React.memo(MemoizationComponent)

CommonComponent와 내용은 동일하지만 export 시점에 React.memo로 감싼 형태인 MemoizationComponent를 새로 만들었다. CommonComponent 대신 MemoizationComponent를 사용해 동일한 코드를 실행해보면 아무리 re-render 버튼을 눌러 리렌더링을 유발해도 MemoizationComponent가 실행되지 않는 것을 확인할 수 있다. 다만, Apple 버튼이나 Meta 버튼을 눌러 이전과 다른 값으로 MemoizationComponent의 props를 바꾸면 MemoizationComponent는 다시 렌더링되게 된다.

정리하자면

  • React.memo를 통해 반환된 컴포넌트는 렌더링을 기점으로 props가 바뀌었는지 여부를 판단한다.
  • 만약 props가 바뀌었다면 해당 컴포넌트를 리렌더링하지만, 바뀌지 않았다면 렌더링하지 않는다.
  • *주의: React.memo는 오직 props가 바뀌었는지 여부만을 판단하기 때문에 컴포넌트 내부의 state가 바뀌어 리렌더링 되는 상황에서는 React.memo와 무관하게 항상 리렌더링 된다.

useMemo, useCallback과의 시너지

React.memo는 홀로 쓰일 때도 강력하지만 때에 따라 다른 최적화 관련 hooks인 useMemo, useCallback 등과 함께 사용될 때 더 큰 시너지를 낼 수 있다. 바로 예시를 보자

import MemoizationComponent from "./MemoizationComponent";
import { useState } from "react";

const App = () => {
  console.log("Running App");

  const [toggleState, setToggleState] = useState(false);

  const someFn = () => {
    console.log("Coooooooool");
  };

  const onRerenderClick = () => {
    setToggleState(!toggleState);
    console.log("re-render!");
  };

  return (
    <>
      <MemoizationComponent someFn={someFn} />
      <button onClick={onRerenderClick}>re-render</button>
    </>
  );
};
export default App;

아까와 동일한 코드지만, MemoizationComponent가 user 대신 someFn이라는 함수를 prop으로 받게 되었다. 이런 상황에서 아까와 똑같이 re-render버튼을 눌러 App 컴포넌트의 렌더링을 유발한다면 MemoizationComponent는 렌더링이 될까? 결론은 ‘그렇다’이다.

얼핏보면 이상할 수 있다. 분명 MemoizationComponent는 React.memo로 감쌌고, someFn 또한 toggleState와 무관하기 때문에 전달되는 porps도 바뀐게 없을텐데 왜 React.memo가 동작하지 않는걸까?
그 이유는 바로 JS에 있다. js는 변수 선언 시점에 string, boolean, number 등의 타입은 변수에 직접 값을 할당하지만, object, array, function 등을 선언할 때에는 별도로 그 내용을 저장한 뒤에 해당 값에 접근 가능한 메모리 주소만을 변수에 할당하기 때문이다. 즉, 리렌더링이 되면서 App 컴포넌트가 다시 실행되면 someFn을 다시 정의하면서 이전과는 다른 메모리 주소를 갖게 되는 것이다. someFn의 내용은 전혀 달라지지 않았지만, 그 참조값이 변경되었기 때문에 react는 이것을 'props가 바뀌었다.'라고 인식하여 MemoizationComponent를 리렌더링한 것이다. 이러한 문제를 해결하기 위해 useCallback이 사용될 수 있다.

  const someFn = useCallback(() => {
    console.log("Coooooooool");
  },[])

이전과 완전히 동일한 코드에 someFn을 useCallback으로 감싸주기만 했을 뿐인데 아까와는 다르게 MemoizationComponent가 실행되지 않는 것을 볼 수 있다.

정리하자면

  • object, array, Function 등의 타입은 매 렌더링마다 참조값이 변경되므로 props가 바뀌었다고 간주, React.memo에도 불구하고 컴포넌트가 렌더링된다.
  • 따라서 이러한 자료형을 props으로 받을 경우 useMemo, useCallback을 적절히 활용하여 React.memo 효과를 극대화할 수 있다.
profile
React-Native 개발블로그

0개의 댓글