React 와 TypeScript 프로젝트에 redux 와 redux-saga 를 붙이는 절차를 파일 단위로 정리한 글이다. 덕스 패턴으로 한 파일에 몰아넣던 모듈이 타입 선언 때문에 300줄을 넘기자 5개 파일로 쪼갰고, typesafe-actions 로 액션 생성과 리듀서 작성을 줄였다. 중복 액션 취소는 saga 의 takeLatest 로 해결한다.
핵심 포인트- 문제는 TypeScript 도입 후 state 타입 선언이 붙으며 덕스 패턴 모듈 한 파일이 300줄을 넘긴 것이었다. 액션 생성 함수가 5개뿐인데도 그랬다.
- 모듈 디렉터리를 actions.ts, types.ts, reducer.ts, saga.ts, index.ts 로 분리해 파일명만 봐도 무엇이 정의돼 있는지 드러나게 했다.
- typesafe-actions 의 createAction 과 ActionType 을 쓰면 액션을 추가해도 타입을 새로 선언할 필요가 없다.
- 비동기는 createAsyncAction 으로 요청, 성공, 실패 세 액션을 한 번에 만들고 saga 에서 call 로 API 를 호출한 뒤 put 으로 결과를 디스패치한다.
- takeLatest 를 써서 같은 액션이 진행 중일 때 새 요청이 오면 이전 것을 취소한다. 중복 액션 처리 문제가 이걸로 풀렸다.
상세 정리- 설치는 CRA TypeScript 템플릿에 redux, react-redux, @types/react-redux, redux-saga, typesafe-actions 를 더하는 구성이다.
- 스토어는 combineReducers 로 rootReducer 를 만들고 ReturnType 으로 RootState 타입을 뽑아 컴포넌트에서 재사용한다.
- 미들웨어 등록을 나중에 편하게 하려고 configureStore.ts 를 미리 분리해둔다.
- actions.ts 는 액션 타입 상수를 as const 로 선언하고 createAction 으로 생성 함수를 만든다. createAction 은 인수 개수 제한이 없다.
- types.ts 에는 스토어에 저장될 state 타입과 ActionType 으로 뽑은 액션 타입을 둔다.
- reducer.ts 는 createReducer 로 switch 대신 객체 형태로 작성한다. 정의된 타입 밖의 값은 애초에 못 쓰기 때문에 값이 없거나 정의되지 않은 데이터로 인한 실수가 줄어든다.
- API 레이어는 api/test/type.ts 에 파라미터와 응답 타입을, api.ts 에 axios 호출을 두고 응답 타입을 제네릭으로 지정한다.
- saga.ts 는 제너레이터 함수라 자바스크립트 제너레이터 이해가 전제된다. try 안에서 call 로 API 를 부르고 성공하면 success, 예외면 failure 를 put 한다.
- 이펙트 정리: call 은 Promise 를 반환하는 함수를 동기적으로 호출하고, fork 는 같은 일을 비동기로 한다. put 은 dispatch 와 동일하다.
- take 계열 정리: takeEvery 는 호출되는 모든 액션을 수신하고, takeLatest 는 진행 중인 같은 액션을 취소한 뒤 새 요청을 처리하며, takeLeading 은 완료될 때까지 같은 액션 호출을 차단한다.
- rootSaga 에서 all 로 여러 사가를 병렬 실행하고, configureStore 에서 createSagaMiddleware 로 만든 미들웨어를 applyMiddleware 로 걸고 sagaMiddleware.run(rootSaga) 를 호출한다.
- 컴포넌트에서는 useSelector 로 스토어 값을 읽고 useDispatch 로 비동기 액션의 request 를 디스패치한다.
- 저자는 redux-saga 가 유일한 정답이 아니라며 redux-observable 이나 MobX 같은 대안도 있으니 프로젝트 로직에 맞는 걸 고르라고 덧붙인다.
왜 읽나TypeScript 를 얹은 뒤 redux 모듈이 비대해진 프론트엔드 개발자에게 파일 분리 구조와 saga 이펙트 선택 기준을 통째로 보여주는 셋업 가이드.