Miracle Morning, LHWN

17. React.memo 를 이용하여 컴포넌트 리렌더링을 방지하기 본문

IT 기술/[React] 기본

17. React.memo 를 이용하여 컴포넌트 리렌더링을 방지하기

Lee Hye Won 2021. 5. 25. 13:54

# React.memo 를 이용하면, 컴포넌트의 props 가 바뀌지 않았을 때 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있다.

리렌더링이 필요한 상황에서만 하도록 설정할 수 있는 것이다!

// CreateUser.js
import React from 'react';

const CreateUser = ({ username, email, onChange, onCreate }) => {
  return (
...
export default React.memo(CreateUser);
// UserList.js
import React from 'react';

const User = React.memo(function User({ user, onRemove, onToggle }) {
  return (
...
});

function UserList({ users, onRemove, onToggle }) {
  return (
...
  );
}

export default React.memo(UserList);

이렇게 모든 컴포넌트에 React.memo 를 감싸줌으로써 input 을 수정하더라도 UserList 가 렌더링 되지 않는다.

그런데!! User 중 하나라도 수정하면 모든 User 들이 리렌더링되고, CreateUser 도 리렌더링이 되고 있다..

왜 그럴까?

users 배열이 바뀔 때마다 onCreate, onToggle, onRemove 함수가 새로 만들어지기 때문이다. (deps에 users 가 들어있기 때문에

배열이 바뀔 때마다 함수가 새로 만들어진다.)

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

  setInputs({
    username: '',
    email: ''
  });
  nextId.current += 1;
}, [users, username, email]);

const onRemove = useCallback(
  id => {
    // user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
    // = user.id 가 id 인 것을 제거함
    setUsers(users.filter(user => user.id !== id));
  },
  [users]
);
const onToggle = useCallback(
  id => {
    setUsers(
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  },
  [users]
);

이걸 또 최적화 하기 위해서는 deps 에서 users 를 지우고, 함수 안에서 현재 useState 로 관리하는 users를 참조하지 않게 하는 것이다.

이는 함수형 업데이트를 통해 구현이 가능하다. 함수형 업데이트를 하면 setUsers 에 등록하는 콜백함수의 파라미터에서 최신 users 를 참조할 수 있기 때문에 deps 에 users 를 넣지 않아도 된다.

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

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 => ({
      ...inputs,
      [name]: value
    }));
  }, []);
  const [users, setUsers] = useState([
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]);

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

    setInputs({
      username: '',
      email: ''
    });
    nextId.current += 1;
  }, [username, email]);

  const onRemove = useCallback(id => {
    // user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
    // = user.id 가 id 인 것을 제거함
    setUsers(users => users.filter(user => user.id !== id));
  }, []);
  const onToggle = useCallback(id => {
    setUsers(users =>
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  }, []);
  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onRemove={onRemove} onToggle={onToggle} />
      <div>활성사용자 수 : {count}</div>
    </>
  );
}

export default App;

# 리액트 개발을 할 때 useCallback, useMemo, React.memo 는 컴포너느의 성능을 실제로 개선할 수 있는 상황에서만 사용해야 한다.

가령, User 컴포넌트에 b 와 button 에 onClick 으로 설정해준 함수들은 useCallback 으로 재사용한다고 해서 리렌더링을 막을 수 있는 것은 아니므로 굳이 사용할 필요가 없다.

또한, 렌더링 최적화하지 않을 컴포넌트에 React.memo 를 사용하는 것은 불필요한 props 비교만 하는 것이기 때문에

실제로 렌더링을 방지할 수 있는 상황에만 사용해야 한다.

# React.memo 에서 특정 값들만 비교하기

export default React.memo(
  UserList,
  (prevProps, nextProps) => prevProps.users === nextProps.users
);

두 번째 파라미터에 propsAreEqual 함수를 이용해서 특정 값들만 비교할 수도 있다.

하지만, 잘못 사용할 경우 의도치 않은 버그들이 발생할 수 있다. 함수형 업데이트로 전환을 안했는데 이렇게 users 만 비교하게 되면,

onToggle 과 onRemove 에서 최신 users 배열을 참조하지 않으므로 심각한 오류가 발생할 수 있다.


출처 : https://react.vlpt.us/basic/19-React.memo.html

 

19. React.memo 를 사용한 컴포넌트 리렌더링 방지 · GitBook

19. React.memo 를 사용한 컴포넌트 리렌더링 방지 이번에는, 컴포넌트의 props 가 바뀌지 않았다면, 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있는 React.memo 라는 함수에 대해

react.vlpt.us

 

Comments