Front-End

vsoghlv@naver.com

React.js react.memo

React.memo 는 컴포넌트에서 리렌더링이 불필요할 때 이전의 컴포넌트 값을 그대로 사용하는 함수이다. React.memo 를 사용하면 컴포넌트의 props 가 바뀌지 않는다면 리렌더링을 하지 않는다. 이 함수 사용으로 컴포넌트에 리렌더링 성능을 최적화 시킬 수 있다.

사용법은 매우 간단한 편인데

//UserList.js
import React, { useEffect } from "react";

const User = React.memo(function User({ user, onRemove, onToggle }) {
  console.log("user렌더링");
  const { username, email, id, active } = user;
  useEffect(() => {
    //console.log("값이 설정됨 :" + user);
    return () => {
      //console.log("값이 바뀌기전 :" + user);
    };
  }, [user]);
  return (
    <div>
      <b
        style=
        onClick={() => onToggle(id)}
      >
        {username} <span>({email})</span>
      </b>
      <button onClick={() => onRemove(id)}>삭제</button>
    </div>
  );
});
function UserList({ users, onRemove, onToggle }) {
  return (
    <div>
      {users.map((user) => (
        <User
          key={user.id}
          user={user}
          onRemove={onRemove}
          onToggle={onToggle}
        ></User>
      ))}
    </div>
  );
}

export default React.memo(UserList);

이런 식으로 React.memo 로 감싸주기만 하면된다.

여기서 문제가 하나 있는데 여러개의 User 컴포넌트 중 하나만 변경되어도 모든 User 컴포넌트가 리렌더링 되는 것이다.

이유를 확인하기 위해 아래 App.js 를 살펴보면

import React, { useRef, useState, useMemo, useCallback } from "react";
import CreateUser from "./CreateUser";
import UserList from "./UserList";

function countActiveUsers(users) {
  console.log("활성 사용자 수를 세고 있습니다.");
  return users.filter((user) => user.active).length;
}

function App() {
  const [inputs, setInputs] = useState({
    username: "",
    email: "",
  });
  const { username, email } = inputs;
  const onChange = useCallback(
    (e) => {
      const { name, value } = e.target;
      setInputs({
        ...inputs,
        [name]: value,
      });
    },
    [inputs]
  );
  const [users, setUsers] = useState([
    {
      id: 1,
      username: "Sun",
      email: "123@naver.com",
      active: true,
    },
    {
      id: 2,
      username: "Jung",
      email: "456@naver.com",
      active: false,
    },
    {
      id: 3,
      username: "Kim",
      email: "789@naver.com",
      active: false,
    },
  ]);

  const nextId = useRef(4);

  const onCreate = useCallback(() => {
    const user = {
      id: nextId.current,
      username,
      email,
    };
    setUsers(users.concat(user));
    setInputs({
      username: "",
      email: "",
    });
    console.log(nextId.current); //4
    nextId.current += 1;
  }, [username, email, users]);

  const onRemove = useCallback(
    (id) => {
      setUsers(users.filter((users) => users.id !== id));
    },
    [users]
  );

  const onToggle = useCallback(
    (id) => {
      setUsers(
        users.map((user) =>
          user.id === id ? { ...user, active: !user.active } : user
        )
      );
    },
    [users]
  );

  const count = useMemo(() => countActiveUsers(users), [users]);

  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      ></CreateUser>
      <UserList
        users={users}
        onRemove={onRemove}
        onToggle={onToggle}
      ></UserList>
      <div>활성 사용자  : {count}</div>
    </>
  );
}

export default App;

UserList 에 props 로 보내는 onRemove 와 onToggle 도 deps 배열에 users 가 들어있다. 결과적으로 users 가 변경되면 onRemove 와 onToggle 도 바뀌게되 리렌더링 되는 결과가 나타나게 되는 것이다.

해결하기 위해서는 props 로 보내는 함수들이 users 를 참조하지 않도록 해야 하는데 함수형 업데이트를 해주면 users 를 참조하지 않아도 된다.

const onCreate = useCallback(() => {
  const user = {
    id: nextId.current,
    username,
    email,
  };
  setUsers((users) => users.concat(user));

  setInputs({
    username: "",
    email: "",
  });
  console.log(nextId.current); //4
  nextId.current += 1;
}, [username, email]);

const onRemove = useCallback((id) => {
  setUsers((users) => users.filter((users) => users.id !== id));
}, []);

setUsers((users) => users.concat(user)); 이렇게 해주면 setUsers 에 등록한 콜백함수의 파라미터(users) 에서 최신 users 를 조회하기 때문에 deps 에 users 를 넣어줄 필요가 없게된다. 결과적으로 onCreate 함수는 username 과 email 이 변경될때만, onRemove 함수는 처음 한번만 렌더링이 되게된다.

React.memo 또한 매번 사용하는 것이 아닌 컴포넌트에 최적화가 필요할지 아닐지에 대한 판단을 정확히 하고 사용해야한다.