Reselect로 Redux 성능 개선하기

Reselect로 Redux 성능 개선하기

Front-end developer WONISM
Interested in ReactJS, RxJS and ReasonML.

셀렉터에 대해

selectorstore로부터 온 데이터를 가져오거나 계산하는 역할을 하며, Redux가 상태를 최소한으로 유지할 수 있도록 한다.
셀렉터는 인자가 변경될 때까지 다시 계산하는 일이 없기에 효율적이다.
(이 원리를 이해하기 위해 메모이제이션에 대해 작성한 글을 참고하면 도움이 될 것이다.)

+
Dan AbramovReselect에 대해 아래 트윗과 같이 말했다.

왜 리셀렉트인가?

Redux를 사용하면서 데이터를 가져올 때, mapStateToProps와 같은 함수를 통해 상태 데이터를 가져온다.

/**
 * STORE
 *
 * {
 *   height: 180,
 *   weight: 60,
 *   bloodPress: { high: 120, low: 80 },
 * }
 */

const getBmi = (height, weight) => (weight / (height ** 2));
const isHypertension = ({ high, low }) => {
  if (high > 140 && low > 90) {
    return true;
  }

  return false;
};

const mapStateToProps = (state) => ({
  bmi: getBmi(state.height, state.weight),
  isHypertension: isHypertension(state.bloodPress),
});

getBmi는 몸무게와 키를 통해 bmi를 구하는 함수이다.
이 함수의 단점은 상태의 일부가 업데이트될 때마다 함수가 데이터를 다시 계산한다는 것이다.
BMI와는 상관 없는 bloodPress가 업데이트되어도 함수가 실행된다.

하지만, Reselect를 사용하면 인자가 변경되지 않으면 재계산을 수행하지 않는다.
리셀렉트를 사용하면 함수 인자가 메모된 함수에 캐시되기 때문이다.
오직 함수의 인자가 이전 호출 때의 값과 다를 경우에만 셀렉터가 다시 계산을 수행한다.

리셀렉트 사용하기

// selectors.js
import { createSelector } from 'reselect';
import fp from 'lodash/fp';

const getHeight = fp.get('height');
const getWeight = fp.get('weight');
const getBloodPress = fp.get('bloodPress');

const getBmi = createSelector(
  // 맨 아래 함수를 제외한 함수들을 Input Selector 라고도 한다.
  getHeight,
  getWeight,
  // 맨 아래 함수를 Result Selector 라고도 한다.
  (height, weight) => (weight / height ** 2)
);

const isHypertension = createSelector(
  getBloodPress,
  ({ high, low }) => {
    if (high > 140 && low > 90) {
      return true;
    }

    return false;
  }
);

const mapStateToProps = state => ({
  bmi: getBmi(state),
  isHypertension: isHypertension(state),
});

아래 예제와 같이 createSelector를 통해 만들어진 셀렉터는 다른 셀렉터의 인자로 사용할 수도 있다.
이는 복잡한 계산을 하기 위한 셀렉터를 작은 여러 셀렉터로 구성하여 읽기 쉽게 작성할 수 있음을 뜻한다.

const isUnderWeight = createSelector(
  getBmi,
  (bmi) => {
    if (bmi < 18) {
      return true;
    }

    return false;
  }
);

정리

어떤 값을 계산함에 있어 불필요한 연산을 수행하지 않을 수 있다는 것만으로도 성능을 개선할 수 있다.
그리고, 셀렉터를 중첩할 수 있다는 것은 코드의 유연함을 제공하며, 리팩토링도 수월해진다.

Reselect의 러닝커브도 그렇게 높지 않기 때문에 프로젝트에 Redux를 사용한다면, Reselect 도입을 추천한다.