당신은하지 않습니다.
그러나 ... 당신은 redux-saga를 사용해야합니다 :)
Dan Abramov의 대답은 옳습니다. redux-thunk
그러나 나는 아주 비슷하지만 더 강력한 redux-saga 에 대해 조금 더 이야기 할 것 입니다.
명령형 VS 선언
- DOM : jQuery는 필수적입니다 ./ 반응은 선언적입니다.
- Monads : IO는 필수입니다 / 무료는 선언적입니다
- 환원 효과 :
redux-thunk
명령 적 / redux-saga
선언적
IO 모나드 또는 약속과 같은 손에 썽크가 있으면, 일단 실행 한 후에는 무엇을하는지 쉽게 알 수 없습니다. 썽크를 테스트하는 유일한 방법은 펑크를 실행하고 디스패처 (또는 더 많은 것들과 상호 작용하는 경우 외부 세계 전체를 조롱하는 것)입니다.
모의를 사용하는 경우 기능 프로그래밍을 수행하지 않는 것입니다.
부작용의 렌즈를 통해 볼 때 모의는 코드가 불완전하다는 플래그이며 함수형 프로그래머의 눈에는 문제가 있음을 증명합니다. 빙산이 손상되지 않았는지 확인하기 위해 라이브러리를 다운로드하는 대신 주변을 항해해야합니다. 하드 코어 TDD / 자바 남자가 Clojure에서 조롱하는 방법을 물었습니다. 대답은 보통 그렇지 않습니다. 우리는 일반적으로 코드를 리팩토링하는 데 필요한 신호로 본다.
출처
사가 redux-saga
)는 선언적이며 Free 모나드 또는 React 구성 요소와 마찬가지로 모의 실험없이 훨씬 쉽게 테스트 할 수 있습니다.
이 기사를 참조하십시오 :
현대 FP에서는 프로그램을 작성해서는 안됩니다. 프로그램에 대한 설명을 작성해야합니다. 그러면 프로그램을 설명하고 변환하고 해석 할 수 있습니다.
(실제로 Redux-saga는 하이브리드와 같습니다. 흐름은 필수적이지만 효과는 선언적입니다)
혼란 : 액션 / 이벤트 / 명령 ...
CQRS / EventSourcing 및 Flux / Redux와 같은 일부 백엔드 개념이 어떻게 관련 될 수 있는지에 대한 프론트 엔드 세계에는 많은 혼란이 있습니다. Flux에서는 종종 명령 코드 ( LOAD_USER
)와 이벤트 ( USER_LOADED
). 이벤트 소싱과 마찬가지로 이벤트 만 전달해야한다고 생각합니다.
실제로 사가 사용
사용자 프로필에 대한 링크가있는 앱을 상상해보십시오. 각 미들웨어로이를 처리하는 관용적 방법은 다음과 같습니다.
redux-thunk
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
이 사가는 다음과 같이 번역됩니다.
사용자 이름을 클릭 할 때마다 사용자 프로필을 가져온 다음로드 된 프로필로 이벤트를 전달하십시오.
보다시피,의 몇 가지 장점이 redux-saga
있습니다.
사용은 takeLatest
마지막으로 클릭 한 사용자 이름의 데이터를 얻는 데에만 관심이 있음을 나타냅니다 (사용자가 많은 사용자 이름을 매우 빠르게 클릭하는 경우 동시성 문제 처리). 이런 종류의 물건은 썽크에 어렵다. takeEvery
이 동작을 원하지 않으면 사용할 수 있습니다 .
액션 크리에이터를 순수하게 유지하십시오. 앞으로 actionCreators (sagas put
및 components dispatch
) 를 유지하는 것이 여전히 유용합니다 . 앞으로 액션 검증 (어설 션 / 플로우 / 유형)을 추가하는 데 도움이 될 수 있습니다.
효과가 선언적이므로 코드를 훨씬 더 테스트 할 수 있습니다.
더 이상 rpc와 같은 호출을 트리거 할 필요가 없습니다 actions.loadUser()
. UI는 HAPPENED를 전달하기 만하면됩니다. 우리는 사건을 발동 하고 (항상 시제로) 더 이상 행동하지 않습니다. 즉, 분리 된 "덕" 또는 바운드 컨텍스트를 만들 수 있으며 saga가 이러한 모듈 식 구성 요소 사이의 연결 지점 역할을 할 수 있습니다.
즉, 발생한 일과 결과로 발생하는 일 사이에 해당 변환 계층을 더 이상 포함 할 필요가 없으므로 뷰를보다 쉽게 관리 할 수 있습니다.
예를 들어 무한 스크롤보기를 상상해보십시오. CONTAINER_SCROLLED
로 이어질 수 NEXT_PAGE_LOADED
있지만 실제로 다른 페이지를로드 해야하는지 여부를 결정하는 것은 스크롤 가능한 컨테이너의 책임입니까? 그런 다음 마지막 페이지가 성공적으로로드되었는지 또는 이미로드하려는 페이지가 있는지 또는 더 이상로드 할 항목이 없는지 여부와 같은 더 복잡한 내용을 알고 있어야합니까? 나는 그렇게 생각하지 않습니다 : 재사용 성을 극대화하기 위해 스크롤 가능한 컨테이너는 스크롤되었다는 것을 설명해야합니다. 페이지로드는 해당 스크롤의 "비즈니스 효과"입니다
일부는 생성자가 로컬 변수를 사용하여 redux 저장소 외부의 상태를 본질적으로 숨길 수 있다고 주장 할 수 있지만 타이머 등을 시작하여 썽크 내부의 복잡한 것을 조정하기 시작하면 어쨌든 동일한 문제가 발생합니다. 그리고 select
이제 Redux 스토어에서 상태를 가져올 수 있는 효과가 있습니다.
Sagas는 시간 여행이 가능하며 현재 진행중인 복잡한 흐름 로깅 및 개발 도구도 사용할 수 있습니다. 다음은 이미 구현 된 간단한 비동기 흐름 로깅입니다.
디커플링
Sagas는 redux 썽크 만 교체하지 않습니다. 백엔드 / 분산 시스템 / 이벤트 소싱에서 나옵니다.
sagas가 redux 썽크를 더 나은 테스트 가능성으로 대체하기 위해 여기에 있다는 것은 매우 일반적인 오해입니다. 실제로 이것은 redux-saga의 구현 세부 사항입니다. 선언적 효과를 사용하는 것이 테스트 가능성을 위해 썽크보다 낫지 만 사가 패턴은 명령형 또는 선언적 코드 위에 구현 될 수 있습니다.
우선 saga는 장기 실행 트랜잭션 (최종 일관성)과 서로 다른 경계 컨텍스트 (도메인 기반 디자인 전문 용어)를 통한 트랜잭션을 조정할 수있는 소프트웨어입니다.
프론트 엔드 세계에서이를 단순화하려면 widget1과 widget2가 있다고 가정하십시오. widget1의 일부 단추를 클릭하면 widget2에 영향을줍니다. widget1은 위젯 2 개를 함께 결합하는 대신 (즉, widget1은 widget2를 대상으로하는 조치를 전달 함) 단추를 클릭 한 것만 전달합니다. 그런 다음 saga는이 단추 클릭을 청취하고 widget2가 인식하는 새 이벤트를 표시하여 widget2를 업데이트하십시오.
따라서 간단한 앱에는 필요하지 않은 간접 수준이 추가되지만 복잡한 애플리케이션을보다 쉽게 확장 할 수 있습니다. 이제 widget1 및 widget2를 다른 npm 저장소에 공개하여 글로벌 조치 레지스트리를 공유하지 않고도 서로에 대해 알 필요가 없습니다. 2 개의 위젯은 이제 별도로 존재할 수있는 제한된 컨텍스트입니다. 서로 일관 될 필요가 없으며 다른 앱에서도 재사용 할 수 있습니다. saga는 비즈니스에 의미있는 방식으로 조정하는 두 위젯 간의 연결점입니다.
Redux 앱을 구성하는 방법에 대한 멋진 기사로, 디커플링 이유로 Redux-saga를 사용할 수 있습니다.
구체적인 사용 사례 : 알림 시스템
구성 요소가 인앱 알림 표시를 트리거 할 수 있기를 원합니다. 그러나 구성 요소가 자체 비즈니스 규칙 (동시에 표시되는 최대 3 개의 알림, 알림 대기열, 4 초 표시 시간 등)이있는 알림 시스템에 고도로 결합되기를 원하지 않습니다.
JSX 구성 요소가 알림을 표시하거나 숨길 시간을 결정하고 싶지 않습니다. 나는 단지 알림을 요청하는 능력을 부여하고, 사가 안에 복잡한 규칙을 남겨 둡니다. 이런 종류의 물건은 썽크 나 약속으로 구현하기가 매우 어렵습니다.
여기 에 saga로 어떻게 할 수 있는지 설명 했습니다.
왜 사가라고 불리는가?
사가라는 용어는 백엔드 세계에서 비롯됩니다. 나는 처음에 야사 인 (Redux-saga의 저자)을 긴 토론 에서 그 용어에 대해 소개했다 .
처음에는이 용어가 논문 과 함께 소개되었는데 , 사가 패턴은 분산 트랜잭션에서 최종 일관성을 처리하는 데 사용되었지만 백엔드 개발자는이를 "프로세스 관리자"도 다루도록 광범위하게 정의했습니다. 패턴 (어떻게도 원래의 사가 패턴은 프로세스 관리자의 특수한 형태입니다).
오늘날, "사가"라는 용어는 두 가지 다른 것을 설명 할 수 있으므로 혼동됩니다. redux-saga에서 사용되므로 분산 트랜잭션을 처리하는 방법이 아니라 앱의 작업을 조정하는 방법을 설명합니다. redux-saga
라고도 할 수 있습니다 redux-process-manager
.
또한보십시오:
대안
생성기를 사용하는 아이디어는 마음에 들지 않지만 saga 패턴 및 디커플링 특성에 관심이있는 경우 이름 을 사용 하여 정확히 동일한 패턴을 설명하지만 RxJS 를 사용하는 redux-observable 을 사용하여 epic
동일한 결과를 얻을 수도 있습니다. 이미 Rx에 익숙하다면 집에있는 것처럼 느낄 것입니다.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
redux-saga 유용한 자료
2017 년 조언
- Redux-saga를 사용하기 위해 과용하지 마십시오. 테스트 가능한 API 호출만으로는 가치가 없습니다.
- 가장 간단한 경우 프로젝트에서 썽크를 제거하지 마십시오.
yield put(someActionThunk)
말이 되더라도 주저하지 마십시오 .
Redux-saga (또는 Redux-observable) 사용에 두려워하지만 디커플링 패턴이 필요한 경우 redux-dispatch-subscribe를 확인하십시오 : 리스너에서 디스패치를 듣고 새 디스패치를 트리거 할 수 있습니다.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});