React 두뇌 풀가동 #11

CoderS·2021년 12월 16일
0

리액트 Remind

목록 보기
11/32

#11 리액트의 늪

React.memo 함수 사용하기

이번에는 컴포넌트 props가 바뀌지 않았다면, 리렌더링 하는 것을 방지하여 컴포넌트의 성능 최적화를 도와주는 React.memo 함수에 대해 알아보겠다.

이 함수를 사용하면, 컴포넌트에서 리렌더링이 필요한 상황만 다시 렌더링을 한다.

CreateUser에 적용해보겠다.

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

CreateUser 컴포넌트를 감싸서 export 해준다.

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.email})</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]
);

deps 안에 users가 들어있기 때문에 배열이 바뀔때마다 함수가 새로 만들어진다.

해결방법은 deps에서 users를 지우고, 함수들에서 현재 useState 로 관리하는 users 를 참조하지 않게 하는것이다.

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

우선 App 컴포넌트를 업데이트 해준다.

App.js

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는 컴포넌트의 성능을 실제로 개선할수있는 상황에만 사용한다.

중요한 사실은 React.memo 에서 두번째 파라미터에 propsAreEqual 이라는 함수를 사용하여 특정 값들만 비교를 하는 것도 가능하다.

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

결론

React.memo는 언제 써야할까?

  • Pure Functional Component에서
  • Rendering이 자주일어날 경우
  • re-rendering이 되는 동안에도 계속 같은 props값이 전달될 경우
  • UI element의 양이 많은 컴포넌트의 경우

React.memo()로 컴포넌트를 감싸게하면, React는 컴포넌트를 렌더링 하고 그 결과를 메모이징(Memoizing) 한다.
만약 렌더링이 일어났을 때 컴포넌트의 props가 같다면, 리액트는 메모이징 된 내용을 재사용한다.

React에서는 성능 개선을 위한 하나의 도구로 React.memo를 사용한다.
React.memo()는 함수형 컴포넌트에서 메모제이션의 장점을 얻게해주는 도구이다. 만약 올바르게 적용이 된다면, 변경되지 않은 동일한 props에 대해 리렌더링 하는것을 막아준다. 다만 콜백함수를 props로 사용하는 컴포넌트에서 React.memo를 할 때 주의해야 한다. 이전과 동일한 콜백함수 인스턴스를 넘기는지 꼭 체크 해야한다.

참고 : 벨로퍼트와 함께하는 모던 리액트

느낀점 :

  • 오늘은 컴포넌트의 성능 최적화 할 수 있게끔 도와주는 React.memo에 대해 알아봤다.
  • 전에 배운 useMemo와 useCallback과 비슷한 면이 있어서 주의하고 사용해야겠다.
profile
하루를 의미있게 살자!

0개의 댓글