redux + reselect

redux를 사용하다 보면 자연스럽게 하게 되는 고민이 있다. 서버로부터 받은 데이터를 가공해야 하는데 어디서 하지? 단순하게 생각하면 reducer에서 가공해서 store에 저장하거나, render 함수 안에서 할 수도 있다.

예를 들어, 서버로부터 todoList를 받는데, 각 todo object는 다음과 같은 속성값이 있다고 하자.

  • title
  • state: 대기, 처리중, 완료
  • priority: 상, 중, 하

사용자는 UI에서 다음 6가지 목록중 하나를 선택할 수 있다고 하자.

  • 모든 대기 항목, 모든 처리중 항목, 모든 완료 항목
  • 모든 우선순위 상 항목, 모든 우선순위 중 항목, 모든 우선순위 하 항목

reducer에서 6가지 목록을 저장할 수도 있고, render 안에서 그때그때 계산할 수도 있다. 각 방법을 조금 더 자세히 살펴보자.

방법 1: reducer에서 가공하기

모든 목록을 미리 저장하므로 메모리를 많이 사용하게 된다. 만약 state와 priority의 가짓수가 많다면 불가능할 수도 있다. 그런데 더 큰 문제는 같은 데이터가 여러 곳에 중복으로 저장된다는 점이다. 대기 상태이고 우선순위 상인 항목은 모든 대기 항목모든 우선순위 상 항목 두 곳에 저장된다. 이 항목의 title이 변경됐을 때 데이터 동기화 작업이 복잡하고, 또한 일부 목록만 업데이트되는 버그가 생길 수도 있다.

방법 2: render에서 가공하기

장점은 원본 데이터만 저장하면 되기 때문에 메모리를 적게 쓴다. 단점은 매 번 render가 호출될 때마다 계산하기 때문에 렌더링 퍼포먼스가 떨어진다.

데이터 가공은 reselect를 사용하자

reducer로 할 때의 단점은 너무 크다. render로 할 때는 매 번 계산한다는 단점이 있는데, 이 부분을 reselect가 보완해준다. reselect는 함수 호출 시 매개변수의 값이 이전과 같다면 다시 계산하지 않고 이전 결과를 그대로 돌려준다.

import {createSelector} from 'reselect'
const getTodoList = createSelector(
(state, props) => state.todoList,
(state, props) => props.selected_state,
(state, props) => props.selected_priority,
(todoList, selected_state, selected_priority) => {
if (selected_state) {
return todoList.filter(todo => todo.state === selected_state);
} else if (selected_priority) {
return todoList.filter(todo => todo.priority === selected_priority);
} else {
return todoList;
}
}
);

reselect로 생성한 getTodoList 함수는 state와 props를 매개변수로 받아서 todoList를 리턴한다. getTodoList 함수가 호출되면 state와 props로부터 계산에 필요한 세 개의 값을 뽑아낸다. 그리고 그 세 개의 값이 이전 계산에 사용된 값과 같다면 이전 결과값을 그대로 리턴하고, 아니라면 새로 계산한다.

redux에서는 mapStateToProps 안에서 getTodoList 함수를 이용하면 된다.

const mapStateToProps = (state, props) => {
return {
todoList: getTodoList(state, props),
}
};