리덕스 사가 패턴이 정신건강에 도움을 줄 것이다.
이 포스팅 보는 사람들은 기본적인 라이브러리 지식이 있다고 생각한다.
Take And Fork
패턴 리스트 중 가장 일반적이다. 앵귤러1의 watcher와 listener를 사용한거 기억하니?
이 패턴은 action이 dispatch된 후에 어떤 프로세스를 트리거하는데 사용한다.
/* this is the saga you are going to register */
export function* aListenerOnlySaga() {
const somePossibleData = yield take('SOME_ACTION');
yield fork(someOtherSagaProcess);
}
function* someOtherSagaProcess() {
/* Any process calculation you need to do */
}
use case
다양한 정보를 보여주고 현재 선택에 따라 action을 취해야하는데 필요한 브랜치/상태를 지원해야한다.
드롭다운으로 브랜치를 선택할 수 있고 그것과 관련된 정보를 쿼리할 수 있어야한다.
/* Some ugly react component*/
class CompanyDropDown extends React.Component {
state = { company: null, branches: [] };
componentDidUpdate({ company, branches }) {
this.setState(({ company }) => ({ company, branches }));
}
onChangeCompany(company) {
this.props.dispatch('company_change', company);
}
render() {
/* omitted for convenience */
}
}
const mapStateToProps = ({company, branches}) => ({company, branches})
export connect(mapStateToProps)(CompanyDropDown)
렌더 메서드와 리듀서와 같이 어떤 지점에 직접접근해야 하는 코드는 생략한다.
/* somewhere in your code... */
export function* listenForChangeCompany() {
/* this variable holds the argument passed */
const company = yield take('company_change');
yield fork(changeCompanySaga, company);
}
function* changeCompanySaga(company) {
const branchesPerCompany = yield call(getBranchesByCompany, company);
yield put({ type: 'company_change_success', payload: branchesPerCompany });
}
UI와 비즈니스 로직을 분리할 수 있다.
main 이득은 특정 기능을 분리시키고 임의로 재량에 따라 팀에 공개하는 프로세스 카탈로그를 만들 수 있는점이다.(나중에 더 자세히 얘기하자).
하지만 이 패턴에 약간의 문제가 있다. 딱 한번 동작한다는 점이다 다음 패턴은 간단하게 이 문제를 해결한다.
Watch and Fork
Take and Fork 패턴의 문제 중 하나는 딱 한번 실행한다는 점이다.
더 나은 fir-for-purpose 케이스는 login or logout 프로세스가 될 수 있다.
마르타가 원할 때마다 회사를 변경할 수 있다고 해보자. watch and fork pattern을 보자.
export function* listenForChangeCompany() {
while (true) {
const company = yield take('company_change');
yield fork(changeCompanySaga, company);
}
} /* eh viola! */
/* Where you register the sagas */ function* rootSagas() {
yield [takeEvery('company_change', changeCompanySaga)];
}
회사 인자는 changeCompanySaga로 전달된다. 이 패턴을 좋아한다. 특히, 수많은 프로세스를 다루는 큰 어플리케이션이라면 더더욱 너는 단지 단일 액션에 대해 응답한다는 점만 알아두면 된다.
Put and Take
이 패턴도 유용하다. 전에 말했듯이, 다양한 사가들로 동작을 구성할 수 있다. teams/people/units간에 모든 것을 공유할 수 있는 서비스 사가를 만들 수 있다.
각 서비스는 상태를 변경시키는 유한한 기능을 가질 수 있다는 뜻이다.
재사용할 수 있는 복잡한 서비스가 만들어져있다고 생각해보자. fetchDataOverFiveDifferentLocations
라고 불리는 게 있다. 많은 암시적인 역할을 할거 같다. 그러나 결국에는 파싱하고 소비할 준비가 된 모든 정보게 제공된다.
팀에서 다음과 같이 이름을 정하기로 했다. {service_name}_{microservice}_{status}
fetchSomeData_events
사가를 시작한다.fetchSomeData_events_start
시작하자마자 서비스에 디스패치되는 액션이다.fetchSomeData_events_success
종료됐을 때 디스패치되는 액션fetchSomeData_events_error
처리 중에 에러가 있다면 디스패치되는 액션
export function* fetchDataOverFiveDifferentLocations() {
while (true) {
yield put({
type: 'fetchSomeData_events_start',
}); /* computing stuff... */
yield put({ type: 'fetchSomeData_events_success' });
}
}
function* rootSagas() {
yield [
takeEvery('fetchSomeData_events', fetchDataOverFiveDifferentLocations),
];
}
만약 위 함수를 확장해야한다면?
/* We create a manager saga */
function* fetchDataManager() {
/* we need to start the service/saga */
yield put({
type: 'fetchSomeData_events',
});
/* we need to wait/listen when it ends...*/
yield take('fetchSomeData_events_success');
/* fork another process, query info from the state, do imperative stuff, whatever you need to do when the previous saga finishes, the sky is the limit... */
}
/* We create an orchestrator saga */
function* orchestratorSaga() {
while (true) {
yield fork(fetchDataManager);
}
}
/* your root saga then looks like this */
function* rootSagas() {
yield [takeEvery('other_action_trigger', orchestratorSaga)];
}
for/of collection
위 방법으로 대부분의 문제를 해결하지 않는다.
어떤 곳에서 collection 데이터를 가져온다고 해보자. 100개의 객체를 받고 각각에게 operation/service를 적용해야한다. 즉, 1개 잇아의 액션을 각 엘리먼트에 디스패치 해야한다. 보통, 리듀서에서 다룰 수 있지만, service catalog의 spirit을 유지하자(?)
saga에서 다음 행동을 할 수 없다.
function* someSagaName() {
/* code omitted for convenience */
const events = yield call(fetchEvents)
events.map((event) => {
/* this is syntactically invalid */
yield put({type: 'some_action', payload: event})
})
}
function* someSagaName() {
/* code omitted for convenience */
const events = yield call(fetchEvents);
for (event of events) {
yield put({ type: 'some_action', payload: event });
/* ?? */
/* or maybe something like: */
yield fork(someOtherSagaOrService, event); /* ?? */
}
}
summary
watch and fork pattern
특정 기능을 분리시킬 수 있다.
put and take
다양한 사가들로 동작을 구성 가능
비동기 API 호출이 있다면
{service_name}_{microservice}_{status}
fetchSomeData_events
사가를 시작한다.fetchSomeData_events_start
시작하자마자 서비스에 디스패치되는 액션이다.fetchSomeData_events_success
종료됐을 때 디스패치되는 액션fetchSomeData_events_error
처리 중에 에러가 있다면 디스패치되는 액션
위 사가를 확장해야한다면?
/* We create a manager saga */
function* fetchDataManager() {
/* we need to start the service/saga */
yield put({
type: 'fetchSomeData_events',
});
/* we need to wait/listen when it ends...*/
yield take('fetchSomeData_events_success');
/* fork another process, query info from the state, do imperative stuff, whatever you need to do when the previous saga finishes, the sky is the limit... */
}
/* We create an orchestrator saga */
function* orchestratorSaga() {
while (true) {
yield fork(fetchDataManager);
}
}
Last updated
Was this helpful?