Redux Observable 간단 정리 하기
Rudux 에서 비동기 처리가 필요하던 중 RxJS 와 함께 쓸 수 있는 Rudux Observable 을 알게 되여 이를 정리해보고자 한다.
Rudux Observable 은 RxJS 기반의 Rudux Middleware 다.
Rudux 미들웨어 는 dispatch 가 호출되었을때, Action 을 가로채, 작업을 처리하고, Next 로 다음 Middleware 에게 Action 을 전달한다.
기본 Next 는 RootReducer 이다.
// redux-thunk
const createThunkMiddleware = ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
Redux Observable 개요
Redux Observable 는 RxJS 기반으로 Rudux Middleware 를 구현하였다.
dispatch 가 호출 되었을때, 구독하고 있는 Epic 들이 원하는 Action 이 있다면, Action 을 Observable 로 작업을 처리하고, 다시 store.dispatch 로 Action 을 호출하는 방법으로 구성되어 있다.
epic(action$, state$).subscribe(store.dispatch)
이때 Action 은 동기 Reducer 에서 먼저 처리하고, 이후 Action 을 구독중인 비동기 Reducer 인 Epic 들이 다음으로 처리한다.
const createEpicMiddleware = (store) => (next) => {
const epic$ = new Subject();
// ...
epic$.subscribe(store.dispatch);
return action => {
const result = next(action);
stateSubject$.next(store.getState());
actionSubject$.next(action);
return result;
};
};
Redux Observable 간단한 예제
Redux Observable 에서는 Action 을 처리하는 방법을 Epic 이라고 정의했다.
Epic 의 Action 이나 State 는 모두 Observable 로 구성되어 있기 때문에, RxJS 를 이해하고 있다면, 매우 쉽게 사용할 수 있다.
아래는 간단한 예제다.
const pingEpic = action$ => action$.pipe(
ofType('PING'),
// filter(action => action.type === 'PING'),
mapTo({ type: 'PONG' })
);const pingReducer = (state = { isPinging: false }, action) => {
switch (action.type) {
case 'PING':
return { isPinging: true };
case 'PONG':
return { isPinging: false };
default:
return state;
}
};// ...
dispatch({ type: 'PING' });
과정
- dispatch({ type: ‘PING’ }) 를 호출하여, 새로운 action을 발생시킨다.
- reducer 에서 action.type 이 ‘PING’ 을 확인하고 새로운 state 를 반환한다.
- epic 에서 action.type 이 ‘PING’ 이므로, 다음 Stream 을 진행한다.
- pipe 가 끝이 나면, 반환된 값을 dispatch({ type: ‘PONG’ }) 를 호출하여, 새로운 action을 발생시킨다.
- reducer 에서 action.type 이 ‘PONG’ 을 확인하고, 새로운 state 를 반환한다.
- epic 에서 원하는 action.type 이 ‘PONG’ 아니므로 무시한다.
Redux Observable Epic
Redux Observable 의 Epic 은 아래와 같이 정의되어 있다.
function (
action$: Observable<Action>,
state$: StateObservable<State>)
dependencies?: Dependencies
): Observable<Action>;
action$ 과 (store 에서 가져온 이전) state$ 는 Observeble 로 제공되며, 반환자는 반드시 Observable 이어야 한다.
(action$, state$) => action$.pipe(...)
반환된 값을 dispatch 다시 Action 을 Reducer 에게 전달되므로, 잘못 사용하게되면, 무한하게 호출될 수 있다.
Epic 에서는, Action type 을 구분하기위한, 특별한 operator 가 구현되어 있다.
ofType 는 Action.type 을 확인하고 다음 Stream 의 진행 여부를 결정할 수 있는데, RxJS 의 Filter 와 비슷하다.
ofType 은 쓰지 않아도 무방하지만, 사용하면 일관된 형태를 강제로 유지할 수 있다.
function ofType(...types): OperatorFunction<Input, Output> {
return filter((action): action is Output => {
const { type } = action;
const len = types.length; if (len === 1) {
return keyHasType(type, types[0]);
} else {
for (let i = 0; i < len; i++) {
if (keyHasType(type, types[i])) {
return true;
}
}
} return false;
});
}const keyHasType = (type, key) => {
return type === key || (typeof key === 'function' && type === key.toString());
};
Redux Observable 설정
Redux Observable 에서는 Redux 의 combineReducers 와 비슷하게, combineEpics 으로 Epic 들을 그룹핑할 수 있다.
최종 Epic 그룹을 epicMiddleware 의 run 함수 에 전달해야만, Epic 들이 Action 구독을 시작한다.
const rootEpic = combineEpics(
aEpic, bEpic
);const epicMiddleware = createEpicMiddleware({
dependencies: { /* some */ }
});const store = createStore(
rootReducer,
applyMiddleware(epicMiddleware)
);epicMiddleware.run(rootEpic);
Redux Observable Dependency
Redux Observable 에서는 Epic 들이 공통적으로 활용할 의존성 객체를 dependencies 로 전달할 수 있다.
createEpicMiddleware 로 Epic Middleware 를 생성할 때, dependence (의존성) 을 주입할 수 있다.
생성할 때 주입한 의존성 객체는 Epic 에서 직접 사용할 수 있다.
const service: Service = process.env.NODE_ENV === 'production' ?
new Service() : ServiceMock;const epicMiddleware = createEpicMiddleware({
dependencies: { service }
});// ...const fetchEpic = (action$, state$, { service }) => action$.pipe(
ofType('FETCH'),
mergeMap(({payload}) => service.fetch$()),
map(payload => ({
type: 'THEN',
payload
})
);
마무리
191208 기준 v1.2.0 은 RxJS 6 버전 이상 과 Redux 4 버전 이상을 지원한다.
아직은 내용을 정리한 단계 이지만, 조금더 활용해서 삽질기나, 고급 스킬을 다음 포스트로 작성할 수 있으면 좋겠다.
Redux Observable 은 RxJS 를 이해하고 있다면, 쉽게 접근할 수 있다.
operator 가 생각이 안날때 아래 링크를 참조하면 편하다.