redux-saga 활용 사례: 0. Intro

sangho.moon·2021년 11월 16일
1

redux-saga

목록 보기
1/3

글에 앞서 저는 리덕스 사가의 광팬임을 밝힙니다.
리덕스 없인 살 수 있어도 리덕스 사가 없이는 살 수 없습니다.

🤔 middleware 왜 써?

상태 관리를 구현하다 보면, 그 상태들과 관련된 비동기적인 작업들이 함께 필요하기 마련인데요. 데이터 페칭이라던지, 외부의 이벤트에 반응해야 한다던지, 작업의 지연이 필요하다던지 말예요. redux만으로는 그러한 복잡한 작업들을 해결할 수 없어 우리는 필연적으로 middleware를 사용하게 됩니다.

저는 Redux-thunk와 Redux-saga를 둘 다 사용해보았는데요. 먼저 thunk는 이해가 쉽고 매우 간단하게 비동기 작업을 구현 가능하다는 장점이 있습니다.

Thunk

// action type
const INCREMENT_COUNTER = 'counter/INCREMENT_COUNTER'

// [Basic] action을 리턴하는 action creator
const increment = () => ({
   type: INCREMENT_COUNTER
});

// [Thunk] function을 리턴하는 action creator
const incrementAsync = () => {
 return (dispatch, getState) => {
   const { counter } = getState();
   
   setTimeout(() => {
     if (counter === 0) dispatch(increment())
   }, 1000)
 }
}

// Counter 컴포넌트가 렌더링되고 1초 후 count 값이 1 증가
function Counter: React.FC () {
 const dispatch = useDispatch();
 const count = useSelector((store) => store.counter);
 
 useEffect(() => {
   dispatch(incrementAsync());
 }, []);
 
 ...
}

위 코드와 같이 기존의 action creator를 dispatchgetState를 매개변수로 받는 function을 리턴하는 함수로만 만들면 thunk가 완성됩니다. 해당 function 내에서 데이터 페칭, 지연 등의 비동기 작업을 구현할 수 있게 된 거죠👏

하지만 thunk를 사용하면서 아쉬운 점이 있었다면 다음과 같습니다

  1. 액션 객체가 너무나 복잡해지는 점 (redux의 디자인 패턴인 Flux 패턴에서는 액션을 typepayload 만을 전달하는 역할로 두기를 제안합니다)
  2. 제공되는 기능 자체가 너무 단순해 복잡한 비동기 작업을 깔끔하게 구현하는 데 제약이 있는 점 (데이터 패칭 실패 후 재시도, 외부 이벤트 채널 구독 등)
  3. 콜백 지옥이 아른아른🤦‍♂️

Saga

thunk의 아쉬운 점들을 살펴보았고 이제 saga로 자연스럽게 넘어갑니다. saga는 ES6에서 새로 등장한 generator 문법을 사용합니다.

// action creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });
const incrementAsync = () => ({ type: INCREMENT_ASYNC });
const decrementAsync = () => ({ type: DECREMENT_ASYNC });

// saga [PUSH Way]
function* incrementSaga() {
 yield delay(1000); // 1초 지연
 yield put(increment()); // "INCREMENT" 액션 dispatch
}

// saga [PULL way]
function* decrementSaga() {
 while (true) { 
   yield take(DECREMENT_ASYNC); // "DECREMENT_ASYNC" 액션 wait
   yield put(decrement()); // "DECREMENT" 액션 dispatch
 }
}

function* counterSaga() {
 // "INCREMENT_ASYNC" 액션을 감시하여 task로 push
 yield takeEvery(INCREASE_ASYNC, increaseSaga);
 // background task로 실행시켜 "DECREMENT_ASYNC" 액션을 pull
 yield decrementSaga();
}

위 코드는 counter를 증가/감소시키는 기능을 각각 다른 방식으로 구현해본 것입니다. generator에 익숙하지 않다면 조금은 낯선 코드일 수 있지만 해당 문법에 익숙해지면 saga의 작동 방식이 조금 이해가 될 거에요. 이후 saga가 제공하는 여러 헬퍼 함수들의 기능을 익힌다면, 복잡하고 까다로운 비동기 작업을 깔끔하게/유지보수하기 쉽게/아름답게 구현이 가능합니다✌️

🤷‍♂️ 그래서 어떻게 쓸 수 있는데?

제공하는 헬퍼 함수들을 아무리 봐도 어떻게 사용할 수 있을지 막연한 분들을 위해, 실제 서비스 기능들을 개발하며 saga를 적용했던 경험을 공유합니다. (휴대폰 인증, 세션 유지, 메세지 구독, router 핸들링 등)

물론 추상화시켜서 대략적인 saga flow 만요😅

가장 기본적인 함수들인 call, put, takeEvery, takeLatest 부터, 다소 까다롭지만 강력한 take, fork, race, eventChannel 등의 함수들까지 왜 적용했고 어떤 점이 좋았는지 등을 공유할 예정이에요.

그럼 다음 포스트로 찾아뵙겠습니다👋

profile
개발자와 디제이 두 개의 자아를 실현 중인 프론트엔드 개발자입니다.

0개의 댓글