selector pattern 적용 및 reselect

휴먼스케이프

개발중인 react-native 프로젝트에 redux와 redux-saga를 적용하게 되었습니다. 컴포넌트로부터 데이터 로직을 분리해서 더 관리하기 좋은 코드를 만들기 위함이었습니다.

프로젝트에 redux를 도입하면서 아래와 같은 절차로 데이터 요청을 수행했습니다.

컴포넌트에서 FETCH_DATA액션을 dispatch합니다.

saga에서 해당 액션을 take하고 API 요청을 수행합니다.

FETCH_DATA_SUCCESS 액션을 dispatch해서 가져온 데이터를 store에 저장합니다.

이 상황에서 컴포넌트가 가져온 데이터를 그대로 사용하거나 일부만 사용할 때에는 별로 어려움이 없었습니다. 하지만, 요청한 데이터를 조합해서 새로운 형태로 가져오거나 계산이 필요한 경우 어디에서 해당 작업을 수행할지 결정하는 것이 애매했습니다.

찾아보니 오늘 글에서 소개할 selector 패턴을 통해 이 문제를 효율적으로 해결할 수 있었습니다. 오늘 글에서는 selector 패턴과 이 패턴을 더욱 효율적으로 적용할 수 있는 reselect 라이브러리에 대해서 소개하겠습니다.

Selector

이 글에서는 store에 저장하는 액션을 setter, selector를 getter에 비유하셨습니다. 찾아본 글 중 가장 와닿고 이해하기 쉬운 표현이라고 생각합니다.

selector는 store에 저장된 state에서 필요한 데이터를 선별적으로 가져오거나, 계산을 수행해서 원하는 형태의 데이터를 가져오는 일을 합니다.

컴포넌트 A와 컴포넌트 B에서 동일한 API를 요청합니다. 이 때 응답결과를 서로 다른 형태로 사용하는 경우를 생각해봅시다. 컴포넌트 A에서는 결과 중 일부를 계산해서 사용해야 하고, 컴포넌트 B에서는 응답받은 결과를 그대로 사용합니다.

컴포넌트 A만 고려하고 개발을 먼저 진행해서 응답 결과를 컴포넌트 A에 필요한 형태로 계산하여 store에 저장 했다고 생각해봅시다. 이후 컴포넌트 B를 개발할 때, store에 원래 형태의 요청결과를 가지고 있는 또 다른 필드를 추가해야 합니다. 이런 컴포넌트가 여러 개 생긴다면 store에 저장되는 데이터들이 늘어나고 관리가 어려워질 것입니다.

selector pattern을 사용하면 요청결과는 store에 저장하고, 컴포넌트에 필요한 데이터 계산(또는 조합)은 selector에게 맡기기 때문에 store를 보다 효율적으로 관리할 수 있습니다.

Selector Example

좀 더 쉬운 이해를 위해서 간단한 예제를 준비했습니다.

FETCH 액션으로 설문(poll) 목록 API를 요청하고 그 결과를 store에 저장하는 상황을 예시로 들어보겠습니다. MyComponent에서는 종료되지 않은 설문(!poll.is_done)들만 컴포넌트에 제공하고 싶습니다.

[주의] 아래 예제에서 rootReducer 등 redux의 기본적인 예제까지 추가하지 않았습니다. rootReducer에서 아래의 pollReducer를 combineReducer로 합쳐서 사용하는 부분은 생략되어 있습니다!

컴포넌트에서 props로 store의 값을 가져올 때, 만들었던 selector를 통해 가져오면 state에서 필요한 값만 가져오거나 원하는 형태로 계산해서 가져올 수 있게 됩니다. 컴포넌트마다 다른 형태로 값을 가져오더라도 selector만 관리하면 되기 때문에 store와 컴포넌트로부터 조합하는 로직을 분리할 수 있어요 :)

Reselect

하지만 저런 방식으로 selector를 사용하면 state가 변경될 때마다 selector 함수가 매번 실행됩니다. 매 state 변경마다 계산을 수행한다면 많은 데이터를 다룰 때에는 성능 문제가 발생할 수 있습니다. 이 문제를 해결해주는 것이 바로 reselect입니다.

reselect는 위의 selector 역할을 수행하면서 캐싱을 통해 동일한 계산을 방지하여 성능을 향상해줍니다. 파라미터로 전달받은 값이 이전과 같다면, 새롭게 계산하지 않고 저장된 결과 값을 돌려줍니다.

selectFriend는 state에 friend 값이 있으면 반환하고, 없으면 friend의 초기상태를 반환하도록 예외처리를 고려해서 추가된 코드입니다.

reselect도 위에서 만든 selector처럼 state를 넣어주고, state를 가공할 콜백을 전달하면 됩니다. 이렇게 만든 reselect 함수를 컴포넌트에서 import 하여 mapStateToProps에서 state를 전달해 사용하면 됩니다.

React 전문가 한 분께 여쭤보았더니 다음과 같은 경우 reselctor 도입을 고려해보면 좋을 것 같다고 말씀해주셨습니다.

데이터가 많아 캐싱이나 최적화가 필요하다.

테스트 코드를 짜서 테스트를 용이하게 하고 싶다.

필요한 데이터를 주는 로직이 전부 다 서버내 구현되어 있지 않다.

받아온 데이터를 처리해야 할 로직이 많다.

서버자원을 조금 아끼고싶다. — 데이터 일정 부분 필터링, 기존 데이터 값에서 새로운 값을 계산해서 반환

컴포넌트에서 데이터를 파싱하는 로직을 추가하기 보단 파싱된 데이터를 받고 사용만하는 식으로 코드 분리를 하고싶다.

redux를 적용하면서 API로 가져온 데이터를 어떻게 처리해야 할지 고민된다면, 혹은 컴포넌트마다 store에 저장된 값을 다르게 사용하고 있다면 오늘 글에서 다룬 selector pattern과 reselect에 대해 고려해보는 것을 추천드립니다. 아래에는 이 글을 작성하면서 참고했던 좋은 글들을 첨부했습니다. 시간 나실 때 함께 읽어보시면 좋을 것 같습니다 :)

Reference

Reselect를 이용하여 React와 Redux 최적화하기

redux + reselect

Redux — Selector 패턴과 Reselect | Godsenal’s Blog

기업문화 엿볼 때, 더팀스

로그인

/