[React] React.memo를 사용한 컴포넌트 리렌더링 방지

호두파파·2021년 4월 9일
1

React

목록 보기
15/38

본 글은 벨로퍼트의 모던 리액트를 참고했습니다.
원문보기


컴포넌트의 props가 바뀌지 않았다면, 리렌더링을 방지해 컴포넌트의 리렌더링 성능 최적화를 해줄수 있는 React.memo라는 함수에 대해서 알아보자.

이 함수를 사용한다면, 컴포넌트에서 리렌더링이 필요한 상황에서만 리렌더링을 하도록 설정해줄 수 있다.

Just do wrap

사용법은 굉장히 간단하다. 그냥 감싸주면 된다.

CreateUser.js

import React from 'react';

const CreateUser = ({ username, email, onChange, onCreate }) => {
  return (
    <div>
      <input
        name="username"
        placeholder="계정명"
        onChange={onChange}
        value={username}
      />
      <input
        name="email"
        placeholder="이메일"
        onchange={onChange}
        value={email}
      />
      <button onClick={onCreate}>등록</button>
    </div>
  );
};
export default React.memo(CreateUser);

UserList와 User 컴포넌트도 적용할 수 있다.

UserList.js

import React from 'react';

const User = react.memo(function User({user, onRemove, onToggle}) {
  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick{() => onToggle(user.id)
      >
        {user.username}
      </b>
      &nbsp
      <span>({user.emial})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button>
    </div>
  );
});

function UserList({ users, onRemove, onToggle }) {
  return (
    <div>
      {users.map(user => (
        <User
          user={user}
          key={user.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
}

export default React.memo(UserList);

적용을 다 하고 나서, input 을 수정 할 때 하단의 UserList 가 리렌더링이 되지 않는것을 확인할 수 있다. 그러나, User 중 하나라도 수정하면 모든 User들이 리렌더링되고, CreateUser도 리렌더링 되고 있다.

이유는 users 배열이 바뀔때마다 onCreate도 새로 만들어지고, onToggle, onRemove도 새로 만들어지기 때문이다.

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]
);

depsusers가 들어있기 때문에 배열이 바뀔때마다 함수가 새로 만들어지는건 당연하다. 이것을 최적화하기 위해선 deps에서 users를 지우고, 함수들에서 현재 useState로 관리하는 users를 참조하지 않게 하는 것이다.

함수형 업데이트

함수형 업데이트를 하게 되면, setUsers에 들고하는 콜백함수의 파라미터에서 최신 users를 참조할 수 있기 때문에 depsusers를 넣지 않아도 된다.

App.js

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

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

function App() {
  const [input, 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;

추가적으로, 렌더링 최적화 하지 않을 컴포넌트에 React.memo를 사용하는 것은 불필요한 props비교만 하는 것이기 때문에 실제로 렌더링을 방지할 수 있는 상황이 있는 경우에만 사용하는 것이 좋다.

추가적으로 React.memo에서 두번째 파라미터에 propsAreEqual이라는 함수를 사용해 특정값들만 비교를 하는 것도 가능하다.

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

true를 반환하면 리랜더를 방지하고, false를 반환하면 리랜더를 실시한다.

profile
안녕하세요 주니어 프론트엔드 개발자 양윤성입니다.

0개의 댓글