[javascript] ES6 생성기와 함께 redux-saga 사용 vs ES2017과 함께 redux-thunk 사용의 장단점 async / await

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는 위대하고 강력한 도구입니다. 그러나 권력에는 책임이 따른다. 응용 프로그램이 커지면 작업이 전달되기를 기다리는 사람 또는 작업이 전달 될 때 모든 일이 발생하는지 파악하여 쉽게 손실 될 수 있습니다. 반면에 썽 크는 더 단순하고 추론하기 쉽습니다. 하나 또는 다른 것을 선택하는 것은 프로젝트의 유형 및 크기, 프로젝트가 팀 선호를 처리하거나 개발해야하는 부작용의 유형과 같은 많은 측면에 달려 있습니다. 어쨌든 응용 프로그램을 간단하고 예측 가능하게 유지하십시오.


답변

개인적인 경험 :

  1. 코딩 스타일과 가독성을 위해 과거에 redux-saga를 사용하는 가장 큰 장점 중 하나는 redux-thunk에서 콜백 지옥을 피하는 것입니다. 더 이상 중첩을 사용하지 않아도됩니다. 그러나 이제 async / await 썽크가 대중화되면서 redux-thunk를 사용할 때 동기화 스타일로 비동기 코드를 작성할 수 있으며, 이는 redux-think의 개선으로 간주 될 수 있습니다.

  2. redux-saga를 사용할 때, 특히 Typescript에서 훨씬 더 많은 상용구 코드를 작성해야 할 수도 있습니다. 예를 들어, 페치 비동기 함수를 구현하려는 경우 하나의 단일 FETCH 조치를 사용하여 action.js에서 하나의 썽크 단위로 데이터 및 오류 처리를 직접 수행 할 수 있습니다. 그러나 redux-saga에서는 FETCH_START, FETCH_SUCCESS 및 FETCH_FAILURE 작업 및 모든 관련 유형 검사를 정의해야 할 수 있습니다. redux-saga의 기능 중 하나는 이러한 종류의 풍부한 “토큰”메커니즘을 사용하여 효과를 만들고 지시하기 때문입니다. 쉬운 테스트를위한 redux store. 물론 이러한 행동을 사용하지 않고 사가를 쓸 수는 있지만 그것은 썽크와 비슷하게 만듭니다.

  3. 파일 구조의 관점에서, redux-saga는 많은 경우 더 분명한 것으로 보입니다. 모든 sagas.ts에서 비동기 관련 코드를 쉽게 찾을 수 있지만 redux-thunk에서는 동작에서 코드를 볼 필요가 있습니다.

  4. 쉬운 테스트는 redux-saga의 또 다른 가중치 기능 일 수 있습니다. 이것은 정말 편리합니다. 그러나 redux-saga “호출”테스트는 테스트에서 실제 API 호출을 수행하지 않으므로 API 호출 후에 사용할 수있는 단계에 대한 샘플 결과를 지정해야합니다. 따라서 redux-saga로 작성하기 전에 saga 및 해당 sagas.spec.ts를 자세히 계획하는 것이 좋습니다.

  5. 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가있는 발전기를 사용하는 것이지만 나에게는 사각 바퀴가 달린 자전거를 발명하려고하는 것 같습니다.

물론 발전기는 테스트하기가 더 쉽습니다.