본문 바로가기

오픈소스/노드

[Node] React 정리(13) - React.memo을 사용하여 컴포넌트 리렌더링 방지

리액트로 프로젝트를 생성하여 개발하는 과정 중 리액트의 필수 문법 및 기초적인 부분, 심화 과정 등을 정리한 문서입니다. 정리한 부분에는 제가 이해하는 관점에서만 정리를 하였기 때문에 초점은 보여주는 형식이 아닌 제가 필요할 때 사용하려는 목적이 담겨져 있습니다. 전 시간에서는 useCallback 과 같이 React.memo를 사용하여 컴포넌트 리렌더링을 방지해보았습니다. useCallback과 React.memo를 같이 이용하는 예제를 이번에도 해볼 예정입니다. 내용은 전시간이랑 똑같지만 한번 더 복습하는 방향으로 설정하였습니다.

 

 현재 정리부터는 소스코드를 제공할 예정입니다. 제공하는 소스코드를 이용하여 따라하실 수 있고 개념을 확실히 잡을 꺼 같아서 올리게 되었습니다. 소스코드 자료는 아래 링크를 참고해주세요.

https://github.com/libtv/React_Note/tree/master/13.%20react_memo

 

GitHub - libtv/React_Note: This is a notebook organized by React.

This is a notebook organized by React. Contribute to libtv/React_Note development by creating an account on GitHub.

github.com

 

그러면 React.memo의 기초와 사용법 에 대해 배워보도록 합시다.

 

 

# Contents


  • 사용하는 이유
  • React.memo 기초와 사용법

 

 

# 사용하는 이유


 컴포넌트의 props 가 바뀌지 않았다면, 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있는 React.memo 라는 함수에 대해서 알아보겠습니다. 이 함수를 사용한다면 컴포넌트에서 리렌더링이 필요한 상황에서만 리렌더링을 하도록 설정할 수 있습니다. 이 함수는 useCallback과 같이 사용하면 더욱 좋다고 들었습니다. 

 

우선, 구글에 React DevTools 를 검색해서 크롬 웹스토어에 들어간뒤, 크롬 확장 프로그램을 설치해주세요. 링크

 

React Developer Tools

Adds React debugging tools to the Chrome Developer Tools. Created from revision e5f486b5a on 10/15/2021.

chrome.google.com

 

그리고 렌더링 되는 컴포넌트를 확인하기 위하여 아래 설정을 참고하여주시기 바랍니다. 이 설정을 통해 우리는 어떠한 컴포넌트가 렌더링 되고 있는지를 확인할 수 있습니다.

 

 

 

 그리고 아래와 같이 Input 박스 안에 텍스트를 넣게 되면 UserList들도 같이 변하게 됨을 확인할 수 있습니다. APP.js 의 CreateUser.js 의 onChange 메소드를 통해 useState를 불러오게 되고, 그에 따라 모든 App.js 컴포넌트들이 리랜더링 되었음을 아래 그림을 통해 확인할 수 있었습니다.

 

 

 

이제부터는 이것의 해결방법인 React.memo을 소개할 것입니다.

 

 

# React.memo 기초와 사용법


 useCallback를 사용하기 위해서는 다음과 같은 구문을 사용해야 합니다.

 

import react from "react";

 

React.memo() 는 다음과 같습니다.

  • React.memo()안에 컴포넌트(여기서는 함수)를 인자로 넣는다.

 

구문은 다음과 같습니다.

 

React.memo(컴포넌트)

 

아래 코드를 통해 React.memo를 확인해보세요. UserList.js

 

import react, { useEffect } from "react";

function UserList({ users, onToggle, onDelete }) {
    return (
        <div>
            {users.map((user) => {
                return <UserInfo user={user} onToggle={onToggle} onDelete={onDelete} />;
            })}
        </div>
    );
}

function UserInfo({ user, onToggle, onDelete }) {
    useEffect(() => {
        console.log("user 값이 설정됨");
        console.log(user);
        return () => {
            console.log("user 가 바뀌기 전..");
            console.log(user);
        };
    }, [user]);

    return (
        <div>
            <h2>
                <b
                    style={{
                        backgroundColor: user.active ? "tomato" : "white",
                    }}
                    onClick={() => {
                        return onToggle(user.id);
                    }}
                >
                    {user.id}
                </b>{" "}
                {user.name} {user.email}{" "}
                <button
                    onClick={() => {
                        return onDelete(user.id);
                    }}
                >
                    삭제
                </button>
            </h2>
        </div>
    );
}

export default react.memo(UserList);

 

아래 코드는 React.memo 함수를 사용하였습니다. 이렇게 React 컴포넌트 함수를 React.memo() 함수로 감싸주면 해당 컴포넌트 함수는 props 값이 변경되지 않는 한 다시 호출되지 않습니다. 아까와 마찬가지로 컴포넌트를 랜더링 시키기 위하여 Input 박스에 글자를 넣어보겠습니다.

 

결과 값은 아래와 같습니다.

 

 

 

분명 React.memo() 를 사용하여 UserList 의 컴포넌트들은 리랜더링하지 않음이라고 하였는데 리랜더링이 되고 있습니다. 그 이유는 함수가 다시 사용되기 때문에 리랜더링이 되기 때문인데요. React.memo를 사용할 때 리랜더링을 방지하기 위해 같이 사용하는 함수인 useCallback 을 사용하여 아래처럼 처리하면 리랜더링을 방지할 수 있습니다.

 

다음은 App.js 의 useCallback을 사용한 코드입니다.

 

import "./App.css";
import react, { useRef, useState, useMemo, useEffect, useCallback } from "react";
import UserList from "./UserList.js";
import CreateUser from "./CreateUser";
import MyRoom from "./MyRoom";

function App() {
    const [input, setInputs] = useState({
        name: "",
        email: "",
    });

    const { name, email } = input;

    const [users, setUsers] = useState([
        { id: 0, name: "John", email: "john@gmail.com", active: true },
        { id: 1, name: "Smith", email: "smith@gmail.com", active: false },
        { id: 2, name: "Park", email: "park@gmail.com", active: false },
        { id: 3, name: "Kim", email: "kim@gmail.com", active: false },
    ]);
    var nextId = useRef(5);

    var count = useMemo(() => countActiveUsers(users), users);

    const onChangeCallback = useCallback(
        (e) => {
            onChange(e);
        },
        [input]
    );
    const onClickCallback = useCallback(() => {
        onClick();
    }, [users]);

    const onToggleCallback = useCallback(
        (userId) => {
            onToggle(userId);
        },
        [users]
    );

    const onDeleteCallback = useCallback(
        (userId) => {
            onDelete(userId);
        },
        [users]
    );

    return (
        <div>
            <CreateUser name={name} email={email} onChange={onChangeCallback} onClick={onClickCallback} />
            <UserList users={users} onToggle={onToggleCallback} onDelete={onDeleteCallback} />
            <div>활성화된 사용자 수 : {count}</div>
        </div>
    );

    function onChange(e) {
        var { name, value } = e.target;
        setInputs({ ...input, [name]: value });
    }

    function onClick() {
        console.log("onClick 이 호출되었습니다.");
        setUsers([...users, { id: nextId.current++, name, email, active: false }]);
    }

    function onToggle(userId) {
        setUsers(
            users.map((user) => {
                return userId === user.id ? { ...user, active: true } : user;
            })
        );
    }

    function onDelete(userId) {
        var newArr = [];
        users.map((user) => {
            return user.id !== userId ? newArr.push(user) : "";
        });

        setUsers([...newArr]);
    }

    function countActiveUsers(users) {
        console.log("활성 사용자 수를 세는중...");
        var s = users.filter((user) => {
            return user.active == true;
        });
        return s.length;
    }
}

export default App;

 

결과 값은 아래와 같습니다.

 

 

 

이렇게 해서 useCallback과 React.memo를 사용한 결과물을 획득하였습니다. 리액트를 사용하는 이유는 컴포넌트 단위로 개발할 수 있기 때문에 협업에 아주 많이 사용하는 것으로 알고 있습니다. 하지만 그에 반해 성능이 낮아지게 된다면 아주 큰 에로사항이라고 생각합니다. 

 

리액트의 Hook을 통하여 리팩토링과 성능을 올리는 것이 리액트 개발자의 역할이라고 생각합니다.