redux-saga / redux-saga의 redux 타운에서 최신 아이에 대해 많은 이야기가 있습니다. 작업 듣기 / 배포를 위해 생성기 기능을 사용합니다.
머리를 감싸기 전에 async / await과 함께 redux-saga
사용 redux-thunk
하는 아래의 접근 방식 대신 사용의 장단점을 알고 싶습니다 .
구성 요소는 다음과 같을 수 있으며 평소와 같이 작업을 전달합니다.
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
그런 다음 내 행동은 다음과 같습니다.
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
답변
redux-saga에서 위의 예와 동등한 것은
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST)
try {
let { data } = yield call(request.post, '/login', { user, pass });
yield fork(loadUserData, data.uid);
yield put({ type: LOGIN_SUCCESS, data });
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
가장 먼저 알아야 할 것은 form을 사용하여 api 함수를 호출한다는 것 yield call(func, ...args)
입니다. call
효과를 실행하지 않고와 같은 일반 객체를 만듭니다 {type: 'CALL', func, args}
. 실행은 기능을 실행하고 그 결과로 생성기를 다시 시작하는 redux-saga 미들웨어에 위임됩니다.
주요 이점은 간단한 등식 검사를 사용하여 Redux 외부에서 발전기를 테스트 할 수 있다는 것입니다
const iterator = loginSaga()
assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))
// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
iterator.next(mockAction).value,
call(request.post, '/login', mockAction)
)
// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
iterator.throw(mockError).value,
put({ type: LOGIN_ERROR, error: mockError })
)
우리는 단순히 모의 데이터를 next
반복자 의 메소드에 주입하여 API 호출 결과를 모의하고 있습니다 . 데이터 모의는 모의 함수보다 훨씬 간단합니다.
두 번째로 알아야 할 것은에 대한 호출 yield take(ACTION)
입니다. 썽 크는 각각의 새로운 액션 (예 :)에서 액션 제작자가 호출합니다 LOGIN_REQUEST
. 즉, 액션은 지속적 으로 썽크에 푸시 되며 썽 크는 이러한 액션의 처리를 중지 할시기를 제어 할 수 없습니다.
REDUX – 사가에서, 발전기는 풀 다음 조치를. 즉, 어떤 행동을들을 때와하지 말아야 할시기를 통제 할 수있다. 위의 예제에서 흐름 명령은 while(true)
루프 내부에 배치 되므로 각 들어오는 동작을 수신하여 썽크 푸시 동작을 모방합니다.
풀 접근 방식으로 복잡한 제어 흐름을 구현할 수 있습니다. 예를 들어 다음 요구 사항을 추가한다고 가정합니다.
-
LOGOUT 사용자 조치 처리
-
첫 번째 로그인에 성공하면 서버는
expires_in
필드에 저장된 지연 시간이 지난 토큰을 반환 합니다.expires_in
밀리 초 마다 백그라운드에서 인증을 새로 고침해야합니다. -
API 호출의 결과를 기다릴 때 (초기 로그인 또는 새로 고침) 사용자가 중간에 로그 아웃 할 수 있음을 고려하십시오.
썽 크로 어떻게 구현하겠습니까? 또한 전체 흐름에 대한 전체 테스트 범위를 제공합니까? Sagas에서 어떻게 보일 수 있습니까?
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
try {
const {name, password} = yield take(LOGIN_REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
// user logged out, next while iteration will wait for the
// next LOGIN_REQUEST action
} catch(error) {
yield put( login.error(error) )
}
}
}
위의 예에서는를 사용하여 동시성 요구 사항을 표현하고 race
있습니다. 경우 take(LOGOUT)
승리 경주 (즉, 사용자가 로그 아웃 버튼 클릭). 레이스는 authAndRefreshTokenOnExpiry
백그라운드 작업을 자동으로 취소합니다 . 그리고 통화 authAndRefreshTokenOnExpiry
도중에이 차단 된 경우 call(authorize, {token})
에도 취소됩니다. 취소는 자동으로 아래쪽으로 전파됩니다.
위의 흐름에 대한 실행 가능한 데모를 찾을 수 있습니다
답변
라이브러리 작성자의 철저한 답변 외에도 프로덕션 시스템에서 saga를 사용한 경험을 추가 할 것입니다.
프로 (사가 사용) :
-
테스트 가능성. call ()이 순수한 객체를 반환하기 때문에 sagas를 테스트하는 것은 매우 쉽습니다. 썽크를 테스트하려면 일반적으로 테스트 내에 mockStore를 포함해야합니다.
-
redux-saga에는 작업에 대한 유용한 도우미 기능이 많이 있습니다. saga의 개념은 앱에 대한 일종의 백그라운드 워커 / 스레드를 생성하는 것으로 보입니다.
-
Sagas는 모든 부작용을 처리 할 수있는 독립적 인 장소를 제공합니다. 일반적으로 내 경험에서 썽크 작업보다 수정하고 관리하는 것이 더 쉽습니다.
범죄자:
-
생성기 구문.
-
배울 개념이 많습니다.
-
API 안정성. redux-saga는 여전히 기능 (예 : 채널?)을 추가하고 있으며 커뮤니티는 그리 크지 않습니다. 라이브러리가 언젠가 이전 버전과 호환되지 않는 업데이트를 만드는 경우 문제가 있습니다.
답변
내 개인적인 경험 (sagas와 thunk를 모두 사용하여)에 대한 의견을 추가하고 싶습니다.
Sagas는 테스트하기에 좋습니다.
- 효과로 감싸 진 함수를 조롱 할 필요가 없습니다.
- 따라서 테스트는 깨끗하고 읽기 쉽고 작성하기 쉽습니다.
- sagas를 사용할 때 액션 제작자는 대부분 일반 객체 리터럴을 반환합니다. 또한 썽크의 약속과 달리 테스트하고 주장하는 것이 더 쉽습니다.
Sagas는 더 강력합니다. 하나의 썽크의 액션 제작자에서 할 수있는 모든 것 또한 하나의 사가에서 할 수 있지만 그 반대는 아닙니다 (또는 적어도 쉽지는 않습니다). 예를 들면 다음과 같습니다.
- 액션 / 액션이 파견 될 때까지 기다립니다 (
take
) - 루틴을 기존의 취소 (
cancel
,takeLatest
,race
) - 여러 루틴이 같은 행동을들을 수 있습니다 (
take
,takeEvery
, …)
Sagas는 또한 몇 가지 일반적인 애플리케이션 패턴을 일반화하는 다른 유용한 기능도 제공합니다.
channels
외부 이벤트 소스 (예 : 웹 소켓)를 청취- 포크 모델 (
fork
,spawn
) - 조절판
- …
Sagas는 위대하고 강력한 도구입니다. 그러나 권력에는 책임이 따른다. 응용 프로그램이 커지면 작업이 전달되기를 기다리는 사람 또는 작업이 전달 될 때 모든 일이 발생하는지 파악하여 쉽게 손실 될 수 있습니다. 반면에 썽 크는 더 단순하고 추론하기 쉽습니다. 하나 또는 다른 것을 선택하는 것은 프로젝트의 유형 및 크기, 프로젝트가 팀 선호를 처리하거나 개발해야하는 부작용의 유형과 같은 많은 측면에 달려 있습니다. 어쨌든 응용 프로그램을 간단하고 예측 가능하게 유지하십시오.
답변
개인적인 경험 :
-
코딩 스타일과 가독성을 위해 과거에 redux-saga를 사용하는 가장 큰 장점 중 하나는 redux-thunk에서 콜백 지옥을 피하는 것입니다. 더 이상 중첩을 사용하지 않아도됩니다. 그러나 이제 async / await 썽크가 대중화되면서 redux-thunk를 사용할 때 동기화 스타일로 비동기 코드를 작성할 수 있으며, 이는 redux-think의 개선으로 간주 될 수 있습니다.
-
redux-saga를 사용할 때, 특히 Typescript에서 훨씬 더 많은 상용구 코드를 작성해야 할 수도 있습니다. 예를 들어, 페치 비동기 함수를 구현하려는 경우 하나의 단일 FETCH 조치를 사용하여 action.js에서 하나의 썽크 단위로 데이터 및 오류 처리를 직접 수행 할 수 있습니다. 그러나 redux-saga에서는 FETCH_START, FETCH_SUCCESS 및 FETCH_FAILURE 작업 및 모든 관련 유형 검사를 정의해야 할 수 있습니다. redux-saga의 기능 중 하나는 이러한 종류의 풍부한 “토큰”메커니즘을 사용하여 효과를 만들고 지시하기 때문입니다. 쉬운 테스트를위한 redux store. 물론 이러한 행동을 사용하지 않고 사가를 쓸 수는 있지만 그것은 썽크와 비슷하게 만듭니다.
-
파일 구조의 관점에서, redux-saga는 많은 경우 더 분명한 것으로 보입니다. 모든 sagas.ts에서 비동기 관련 코드를 쉽게 찾을 수 있지만 redux-thunk에서는 동작에서 코드를 볼 필요가 있습니다.
-
쉬운 테스트는 redux-saga의 또 다른 가중치 기능 일 수 있습니다. 이것은 정말 편리합니다. 그러나 redux-saga “호출”테스트는 테스트에서 실제 API 호출을 수행하지 않으므로 API 호출 후에 사용할 수있는 단계에 대한 샘플 결과를 지정해야합니다. 따라서 redux-saga로 작성하기 전에 saga 및 해당 sagas.spec.ts를 자세히 계획하는 것이 좋습니다.
-
Redux-saga는 병렬 작업 실행, takeLatest / takeEvery, 포크 / 스폰 같은 동시성 도우미와 같은 많은 고급 기능을 제공합니다.이 기능은 썽크보다 훨씬 강력합니다.
결론적으로 개인적으로 말하고 싶습니다. 많은 일반적인 경우와 중소형 앱에서 async / await 스타일 redux-thunk와 함께하십시오. 많은 상용구 코드 / 액션 / 타입 정의를 저장하고 여러 가지 sagas.ts를 전환하고 특정 sagas 트리를 유지할 필요가 없습니다. 그러나 매우 복잡한 비동기 로직과 동시성 / 병렬 패턴과 같은 기능이 필요한 대형 앱을 개발하거나 테스트 및 유지 관리 (특히 테스트 중심 개발)에 대한 수요가 높은 경우 redux-sagas는 생명을 구할 수 있습니다. .
어쨌든 redux-saga는 redux 자체보다 어렵고 복잡하지 않으며 핵심 개념과 API가 제한되어 있기 때문에 소위 가파른 학습 곡선이 없습니다. redux-saga를 배우는 데 약간의 시간을 투자하면 앞으로 언젠가는 도움이 될 수 있습니다.
답변
내 경험에서 몇 가지 다른 대규모 React / Redux 프로젝트를 검토 한 Sagas는 개발자에게 테스트하기가 더 쉽고 잘못하기 어려운 코드를 작성하는보다 체계적인 방법을 개발자에게 제공합니다.
그렇습니다. 시작하기에는 조금 어리석지 만 대부분의 개발자는 하루 만에 이해해야합니다. 나는 항상 사람들에게 무엇 yield
을 시작 해야하는지 걱정하지 말라고 말하고 일단 테스트를 두 번하면 당신에게 올 것입니다.
나는 썽크가 MVC 패튼의 컨트롤러 인 것처럼 취급되는 몇 가지 프로젝트를 보았습니다. 이것은 빠르게 관리 할 수없는 혼란이됩니다.
내 조언은 A가 단일 이벤트와 관련된 B 유형 물건을 트리거 해야하는 곳에서 Sagas를 사용하는 것입니다. 여러 가지 조치를 취할 수있는 것은 고객 미들웨어를 작성하고 FSA 조치의 메타 특성을 사용하여 트리거하는 것이 더 쉽다는 것을 알았습니다.
답변
썽크 대 사 가스
Redux-Thunk
그리고 Redux-Saga
몇 가지 중요한 방법으로 다른 두 돌아 오는 미들웨어 라이브러리 (돌아 오는 미들웨어 차단 조치가 발송 () 메소드를 통해 저장소로 들어오는 것을 코드)입니다.
액션은 말 그대로 어떤 것이 든 가능하지만 모범 사례를 따르는 경우 액션은 유형 필드와 선택적 페이로드, 메타 및 오류 필드가있는 일반 자바 스크립트 객체입니다. 예 :
const loginRequest = {
type: 'LOGIN_REQUEST',
payload: {
name: 'admin',
password: '123',
}, };
Redux-Thunk
Redux-Thunk
미들웨어를 사용하면 표준 조치 디스패치 외에도 이라는 특수 함수를 디스패치 할 수 있습니다 thunks
.
썽크 (Redux)는 일반적으로 다음과 같은 구조를 갖습니다.
export const thunkName =
parameters =>
(dispatch, getState) => {
// Your application logic goes here
};
즉, a thunk
는 선택적으로 일부 매개 변수를 사용하고 다른 함수를 반환하는 함수입니다. 내부 함수는 a dispatch function
와 함수를 취합니다. getState
둘 다 Redux-Thunk
미들웨어에서 제공합니다 .
레덕 사가
Redux-Saga
미들웨어를 사용하면 복잡한 애플리케이션 로직을 sagas라는 순수한 함수로 표현할 수 있습니다. 순수한 기능은 테스트 관점에서 바람직하고 예측 가능하고 반복 가능하므로 테스트하기가 비교적 쉽습니다.
Sagas는 발전기 기능이라는 특수 기능을 통해 구현됩니다. 이것들은의 새로운 기능입니다 ES6 JavaScript
. 기본적으로 yield 문을 보는 곳마다 실행이 생성기 안팎으로 점프합니다. yield
명령문이 생성기를 일시 중지하고 생성 된 값을 리턴하는 것으로 생각하십시오 . 나중에 호출자는 다음에 나오는 문에서 생성기를 다시 시작할 수 있습니다 yield
.
생성기 함수는 이와 같이 정의됩니다. function 키워드 다음에 별표가 표시됩니다.
function* mySaga() {
// ...
}
일단 로그인 사가이 등록되어 있습니다 Redux-Saga
. 그러나 yield
첫 번째 줄을 가져 가면 유형 'LOGIN_REQUEST'
이 있는 작업 이 상점에 발송 될 때까지 사가를 일시 중지합니다 . 그렇게되면 실행이 계속됩니다.
답변
하나의 빠른 메모. 제너레이터는 취소 가능하고 비동기 / 대기 가능합니다. 따라서 질문의 예를 들어, 실제로 무엇을 선택 해야하는지 이해하지 못합니다. 그러나 더 복잡한 흐름의 경우 때때로 생성기를 사용하는 것보다 더 나은 솔루션이 없습니다.
그래서 또 다른 아이디어는 redux-thunk가있는 발전기를 사용하는 것이지만 나에게는 사각 바퀴가 달린 자전거를 발명하려고하는 것 같습니다.
물론 발전기는 테스트하기가 더 쉽습니다.