Ryu.log

[React] 성능최적화 (React.memo() 사용팁 정리) 본문

Front-end/React JS

[React] 성능최적화 (React.memo() 사용팁 정리)

류뚝딱 2021. 4. 14. 15:57

React.memo() 사용팁 정리

Created: Apr 14, 2021 10:27 AM
Tags: React, javascript
요약: React.memo() 사용 팁

React.memo() 사용 팁 정리

React에서는 UI성능 증가를 위해 **React.memo()** 를 지원한다.

React.memo()

**React**에서는 먼저 컴포넌트를 랜더링 한 뒤,
이전 랜더링 된 결과와 비교하여 랜더링 결과가 이전과 다르다면, DOM을 업데이트 한다.

export function User({ name, data }) {
  return (
    <div>
      <div>name: {name}</div>
      <div>data: {data}</div>
    </div>
  );
}

export const MemoizedUser = React.memo(User);

React.memo()User컴포넌트를 래핑하게 되면, React는 컴포넌트를 랜더링 하고,
그 결과를 메모이징(Memoizing) 한다.

그 뒤, 다음 랜더링이 일어났을 때 해당 컴포넌트의 props가 같다면,
React는 메모이징 된 내용을 재사용한다.

메모이징 한 결과를 재사용 함으로써, React는 리랜더링 시 가상 DOM에서 달라진 부분을 확인하지 않아 성능이 향상 될 수 있다.

props 비교 커스터마이징

React.memo()props 혹은 **props**의 객체를 비교할 때 얕은 비교를 한다.
깊은 비교 방식이나 다른 방식으로 수정하고 싶을 땐 React.memo()의 두번째 매개변수로 비교함수를 만들어 넘겨주면 된다.

export function User({ name, data }) {
  return (
    <div>
      <div>name: {name}</div>
      <div>data: {data}</div>
    </div>
  );
}

function userPropsEqual(prevUser, nextUser) {
    return prevUser.name === nextUser.name && prevUser.data === nextUser.data
};

export const MemoizedUser = React.memo(User, userPropsEqual);

userPropsEqual() 함수는 prevPropsnextProps 가 같다면 true를 반환할 것이다.

위와같은 방식으로 두번째 매개변수에 비교함수를 넘겨주면 좀 더 상세한 비교가 가능해진다.

React.memo()를 사용해야할 때와 하지말아야 할 때

사용해야 할 때

  • 컴포넌트가 같은 props로 자주 랜더링 될거라 예상될 때.

사용하지 말아야 할 때

성능 관련 변경이 잘못 적용 된다면 성능이 오히려 악화될 수 있다. **React.memo()**를 현명하게 사용해야 한다.

  • 컴포넌트가 다른 props로 자주 랜더링 될거라 예상될 때. (쓸데없는 props 비교)
    • 대부분의 결과가 다르다면 메모제이션 기법의 이점을 얻기 힘들다.
      어짜피 결과가 다르니 React.memo()의 비교함수의 대부분은 false를 반환, 리랜더링이 일어날 것이고,
    • React.memo()*로 래핑하지 않은 컴포넌트와 비교 시, 오히려 이전**props**와 현재 props의 비교작업이 추가되기 때문이다.

React.memo() 와 콜백함수 주의사항

함수 객체는 "일반" 객체와 동일한 비교원칙을 따른다.
함수 객체는 오직 자신에게만 동일하다

function sumFactory() {
    return ( a, b ) => a + b;
}

const sum1 = sumFactory();
const sum2 = sumFactory();

console.log(sum1 === sum2); // false
console.log(sum1 === sum1); // true
console.log(sum2 === sum2); // true

**sumFactory()** 함수는 2가지 숫자를 더해주는 화살표 함수를 반환한다.

sum1sum2 두가지 모두 sumFactory()에 의해 생성된 함수이지만. sum1sum2는 각각 다른 함수 객체이다.

부모 컴포넌트가 자식 컴포넌트에게 Props로 콜백함수를 전달할 때, 새 함수가 암시적으로 생성될 가능성이있다.

function Logout({ name, onLogout }) {
  return <div onClick={onLogout}>Logout {name}</div>;
}

const MemoizedLogout = React.memo(Logout);

function UserContainer({store, cookies}) {
  return (
    <div className="main">
      <header>
        <MemoizedLogout
          name={store.name}
          onLogout={() => cookies.clear()} 
        />
      </header>
      {store.content}
    </div>
  );
}

위 코드에서 동일한 **name**이 전달 되더라도, **MemoizedLogout** 은 새로운 **onLogout** 콜백 때문에 메모제이션이 되지않고 리랜더링을 하게된다.
**onLogout** **Props**로 넘어가는 콜백함수가 inline으로 작성되어(프로그램 구동에는 문제가 없지만) 매번 새로 함수 참조값이 생성되기 때문이다.

해당 부분을 정상적으로 메모제이션 하기 위해서는

function Logout({ name, onLogout }) {
  return <div onClick={onLogout}>Logout {name}</div>;
}

const MemoizedLogout = React.memo(Logout);

function UserContainer({store, cookies}) {
    const onLogout = useCallback(() => {
    cookies.clear();
  }, []);
  return (
    <div className="main">
      <header>
        <MemoizedLogout
          name={store.name}
          onLogout={onLogout} 
        />
      </header>
      {store.content}
    </div>
  );
}

**useCallback()** 을 이용하면 콜백 함수의 메모제이션된 버전을 반환 하여, 매번 동일한 콜백 인스턴스를 반환한다.
이를통해 **MemoizedLogout** 의 메모제이션이 정상동작 가능하다.

결론

React에서는 성능 개선을 위한 하나의 도구로 메모제이션을 사용한다.

대부분의 상황에서 React는 메모이징 된 컴포넌트의 리랜더링을 피할 수 있지만, 랜더링을 막기 위해 메모제이션에 의존하면 안된다.

React.memo()는 함수형 컴포넌트에서 메모제이션의 장점을 얻게해주는 도구이다.
올바르게 적용 된다면, 변경되지 않은 동일한 props에 대해 리랜더링 하는것을 막아준다.

다만 콜백 함수를 props로 사용하는 컴포넌트에서 메모제이징을 할 때 주의해야 한다.
이전과 동일한 콜백함수 인스턴스를 넘기는지 꼭 체크 해야한다.

참고한 사이트

https://ui.toast.com/weekly-pick/ko_20190731

https://velog.io/@yejinh/useCallback과-React.Memo을-통한-렌더링-최적화

'Front-end > React JS' 카테고리의 다른 글

[React] Hooks  (1) 2019.09.26
Comments