전체 방문자
오늘
어제
  • 전체 글
    • HTML
    • CSS
    • Javascript
    • React
    • Typescript
    • Next.js
    • Webpack
    • Vue.js
    • Git & GitHub
    • Error
    • Study
    • 개발 일지✨

블로그 메뉴

  • 💡
  • ⚙️
hELLO · Designed By 정상우.
하루

Home

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

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

2022. 5. 7. 01:47

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

 

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

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

 

사용법

React.memo( ) 안에 컴포넌트(함수)를 파라미터로 넣는다.

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

 

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 를 넣지 않아도 된다.

그럼 각 함수를 업데이트 해보자. (onChange 의 경우엔 함수형 업데이트를 해도 영향은 가지 않지만, 연습삼아 같이 해주었다.)

 

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 는 컴포넌트의 성능을 실제로 개선할 수 있는 상황에서만 사용하는 것이 좋다.
예를 들어서 User 컴포넌트에 b 와 button 에 onclick 으로 설정해준 함수들은, 해당 함수들을 useCallback 으로 재사용한다고해서 리렌더링을 막을 수 있는 것은 아니므로, 굳이 그렇게 할 필요는 없다.
추가적으로, 렌더링 최적화하지 않을 컴포넌트에 React.memo 를 사용하는 것은, 불필요한 props 비교만 하는 것이기 때문에 실제로 렌더링을 방지할 수 있는 상황이 있는 경우에만 사용한다.

 

propsAreEqual 함수

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

이 함수에서는 prevProps(전 함수)와 nextProps(후 함수)를 가져와서 비교한다.

두 함수를 비교해서 true 라면 리렌더링을 방지하고, false 가 나온다면 리렌더링 된다.

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

하지만, 이걸 잘못 사용한다면 오히려 의도치 않은 버그들이 발생하기 쉽다.

예를 들어서, 함수형 업데이트로 전환을 안했는데 이렇게 users 만 비교를 하게 된다면, onToggle 과 onRemove 에서 최신 users 배열을 참조하지 않으므로 심각한 오류가 발생할 수 있다.

이런식으로 함수의 특정 props 를 비교해서 리렌더링 방지를 할 수 있지만, 이 경우에 나머지 props 들이 정말 고정적이기 때문에 비교가 할 필요가 없는 것인지 확인해야 한다.

 

    'React' 카테고리의 다른 글
    • 함수형 업데이트 useState
    • 컴포넌트 최적화 정리 (useMemo/useCallback/React.memo)
    • useCallback 으로 함수 재사용하기
    • useMemo 를 사용하여 연산값 재사용하기

    티스토리툴바