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


488

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...

6
참조 여기 REDUX - 사가에 REDUX-썽크를 비교하는 내 대답 : stackoverflow.com/a/34623840/82609
세바스티앙 Lorber

22
당신이하기 ::전에 무엇입니까 this.onClick?
Downhillski

37
@ZhenyangHua 함수를 객체 ( this), 즉 일명 바인딩하는 속기 this.onClick = this.onClick.bind(this)입니다. 숏 핸드는 모든 렌더에서 리 바인드되므로 일반적으로 생성자에서 긴 형식을 사용하는 것이 좋습니다.
hampusohlsson

7
내가 참조. 감사! bind()많은 사람들 this이 함수 에 전달 하는 것을 보았지만 () => method()지금 사용하기 시작했습니다 .
Downhillski

2
@Hosar 나는 잠시 동안 생산 REDUX & REDUX-무용담을 사용하지만, 실제로 몇 달 후 MobX로 마이그레이션 적은 오버 헤드 때문에
hampusohlsson

답변:


461

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})에도 취소됩니다. 취소는 자동으로 아래쪽으로 전파됩니다.

위의 흐름에 대한 실행 가능한 데모를 찾을 수 있습니다


@yassine delay함수 는 어디 에서 오는가? 아, 그것을 발견했다 : github.com/yelouafi/redux-saga/blob/…
philk

122
redux-thunk코드는 매우 읽기이고 자기 설명했다. 그러나 redux-sagas하나는 주로하기 때문에 그 동사와 같은 기능으로, 정말 읽을 수 call, fork, take, put...
SYG

11
@syg, 나는 전화, 포크, 테이크 및 풋이 더 의미 적으로 친숙하다는 데 동의합니다. 그러나 모든 부작용을 테스트 할 수있는 것은 동사와 같은 기능입니다.
Downhillski

3
@syg 아직도 그 이상한 동사 함수를 가진 함수는 딥
프로 미스

3
이 "이상한"동사는 또한 redux에서 나오는 메시지와 saga의 관계를 개념화하는 데 도움이됩니다. 당신은 할 수 걸릴 다음 반복을 실행하는 데 종종, 당신은 할 수 - REDUX에서 메시지 유형을 넣어 당신의 부작용의 결과를 방송 다시 새 메시지를.
worc

104

라이브러리 작성자의 철저한 답변 외에도 프로덕션 시스템에서 saga를 사용한 경험을 추가 할 것입니다.

프로 (사가 사용) :

  • 테스트 가능성. call ()이 순수한 객체를 반환하기 때문에 sagas를 테스트하는 것은 매우 쉽습니다. 썽크를 테스트하려면 일반적으로 테스트 내에 mockStore를 포함해야합니다.

  • redux-saga에는 작업에 대한 유용한 도우미 기능이 많이 있습니다. saga의 개념은 앱에 대한 일종의 백그라운드 워커 / 스레드를 생성하는 것으로 보입니다.

  • Sagas는 모든 부작용을 처리 할 수있는 독립적 인 장소를 제공합니다. 일반적으로 내 경험에서 썽크 작업보다 수정하고 관리하는 것이 더 쉽습니다.

범죄자:

  • 생성기 구문.

  • 배울 개념이 많습니다.

  • API 안정성. redux-saga는 여전히 기능 (예 : 채널?)을 추가하고 있으며 커뮤니티는 그리 크지 않습니다. 라이브러리가 언젠가 이전 버전과 호환되지 않는 업데이트를 만드는 경우 문제가 있습니다.


9
댄이 여러 번 주장한 액션 크리에이터는 순수한 기능 일 필요는 없다.
Marson Mao

14
현재, redux-sagas는 사용법과 커뮤니티가 확장됨에 따라 매우 권장됩니다. 또한 API가 더욱 성숙해졌습니다. API stability현재 상황을 반영하기 위해 업데이트로 Con for 를 제거하십시오 .
Denialos

1
saga는 썽크보다 더 많은 시작을 가지고 있으며 마지막 커밋도 썽크 후에
amorenew

2
예, FWIW의 REDUX-사가 지금 REDUX-썽크가 8K가, 12K 별이있다
브라이언 번스

3
Sagas의 또 다른 도전은 Sagas 가 기본적으로 액션 및 액션 제작자와 완전히 분리되어 있다는 것 입니다. 썽 크는 액션 크리에이터와 부작용을 직접 연결하지만, 사 가스는 액션 크리에이터를 자신의 말을 듣는 사가 스와 완전히 분리시킵니다. 이것은 기술적 인 장점이 있지만 코드를 따르기가 훨씬 어려워 질 수 있으며 일부 단방향 개념을 흐리게 할 수 있습니다.
theaceofthespade

33

내 개인적인 경험 (sagas와 thunk를 모두 사용하여)에 대한 의견을 추가하고 싶습니다.

Sagas는 테스트하기에 좋습니다.

  • 효과로 감싸 진 함수를 조롱 할 필요가 없습니다.
  • 따라서 테스트는 깨끗하고 읽기 쉽고 작성하기 쉽습니다.
  • sagas를 사용할 때 액션 제작자는 대부분 일반 객체 리터럴을 반환합니다. 또한 썽크의 약속과 달리 테스트하고 주장하는 것이 더 쉽습니다.

Sagas는 더 강력합니다. 하나의 썽크의 액션 제작자에서 할 수있는 모든 것 또한 하나의 사가에서 할 수 있지만 그 반대는 아닙니다 (또는 적어도 쉽지는 않습니다). 예를 들면 다음과 같습니다.

  • 액션 / 액션이 파견 될 때까지 기다립니다 ( take)
  • 루틴을 기존의 취소 ( cancel, takeLatest, race)
  • 여러 루틴이 같은 행동을들을 수 있습니다 ( take, takeEvery, ...)

Sagas는 또한 몇 가지 일반적인 애플리케이션 패턴을 일반화하는 다른 유용한 기능도 제공합니다.

  • channels 외부 이벤트 소스 (예 : 웹 소켓)를 청취
  • 포크 모델 ( fork, spawn)
  • 조절판
  • ...

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


8

개인적인 경험 :

  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를 배우는 데 약간의 시간을 투자하면 앞으로 언젠가는 도움이 될 수 있습니다.


5

내 경험에서 몇 가지 다른 대규모 React / Redux 프로젝트를 검토 한 Sagas는 개발자에게 테스트하기가 더 쉽고 잘못하기 어려운 코드를 작성하는보다 체계적인 방법을 개발자에게 제공합니다.

그렇습니다. 시작하기에는 조금 어리석지 만 대부분의 개발자는 하루 만에 이해해야합니다. 나는 항상 사람들에게 무엇 yield을 시작 해야하는지 걱정하지 말라고 말하고 일단 테스트를 두 번하면 당신에게 올 것입니다.

나는 썽크가 MVC 패튼의 컨트롤러 인 것처럼 취급되는 몇 가지 프로젝트를 보았습니다. 이것은 빠르게 관리 할 수없는 혼란이됩니다.

내 조언은 A가 단일 이벤트와 관련된 B 유형 물건을 트리거 해야하는 곳에서 Sagas를 사용하는 것입니다. 여러 가지 조치를 취할 수있는 것은 고객 미들웨어를 작성하고 FSA 조치의 메타 특성을 사용하여 트리거하는 것이 더 쉽다는 것을 알았습니다.


2

썽크 대 사 가스

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'이 있는 작업 이 상점에 발송 될 때까지 사가를 일시 중지합니다 . 그렇게되면 실행이 계속됩니다.

자세한 내용은이 기사를 참조하십시오 .


1

하나의 빠른 메모. 제너레이터는 취소 가능하고 비동기 / 대기 가능합니다. 따라서 질문의 예를 들어, 실제로 무엇을 선택 해야하는지 이해하지 못합니다. 그러나 더 복잡한 흐름의 경우 때때로 생성기를 사용하는 것보다 더 나은 솔루션이 없습니다.

그래서 또 다른 아이디어는 redux-thunk가있는 발전기를 사용하는 것이지만 나에게는 사각 바퀴가 달린 자전거를 발명하려고하는 것 같습니다.

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


0

여기에서 모두 최고의 부품 (프로)를 결합 프로젝트의 redux-sagaredux-thunk:하여 약속을 가져 오는 동안 당신이 무용담 모든 부작용을 처리 할 수있는 dispatching대응 조치 : https://github.com/diegohaz/redux-saga-thunk

class MyComponent extends React.Component {
  componentWillMount() {
    // `doSomething` dispatches an action which is handled by some saga
    this.props.doSomething().then((detail) => {
      console.log('Yaay!', detail)
    }).catch((error) => {
      console.log('Oops!', error)
    })
  }
}

1
then()React 컴포넌트 내부에서 사용 하는 것은 패러다임에 위배됩니다. componentDidUpdate약속이 해결되기를 기다리는 대신 변경된 상태를 처리해야합니다 .

3
@ Maxincredible52 서버 측 렌더링에는 해당되지 않습니다.
Diego Haz

내 경험상 Max의 요점은 여전히 ​​서버 측 렌더링에 적용됩니다. 이것은 아마도 라우팅 계층 어딘가에서 처리되어야합니다.
ThinkingInBits

3
@ Maxincredible52 패러다임에 반대하는 이유는 무엇입니까? 나는 보통 @Diego 하즈과 유사 할 수 있지만 (문서 반응에 따라, 네트워크 호출이 수행 할 수 바람직합니다으로) 우리는 그래서 componentDidMount에서 할componentDidlMount() { this.props.doSomething().then((detail) => { this.setState({isReady: true})} }
user3711421

0

더 쉬운 방법은 redux-auto 를 사용하는 것입니다 .

망설임에서

redux-auto는 약속을 반환하는 "조치"함수를 만들 수있게함으로써이 비동기 문제를 간단히 해결했습니다. "기본"기능 동작 로직을 동반합니다.

  1. 다른 Redux 비동기 미들웨어가 필요하지 않습니다. 예 : 썽크, 약속 미들웨어, 사가
  2. Redux에 약속을 쉽게 전달하고 관리 할 수 ​​있습니다.
  3. 외부 서비스 요청을 변환 할 위치와 함께 배치 할 수 있습니다
  4. 파일 이름을 "init.js"로 지정하면 앱 시작시 한 번만 호출됩니다. 시작시 서버에서 데이터를로드하는 데 좋습니다

아이디어는 특정 파일에서작업을 수행하는 것 입니다. "보류", "완료"및 "거부"에 대한 감속기 기능을 사용하여 파일에 서버 호출을 배치합니다. 이를 통해 약속을 매우 쉽게 처리 할 수 ​​있습니다.

또한 상태의 프로토 타입에 도우미 개체 ( "async") 를 자동으로 연결 하여 UI에서 요청 된 전환을 추적 할 수 있습니다.


2
나는 +1 심지어 다른 솔루션도 고려되어야하기 때문에 관련이없는 대답했다
amorenew

12
나는 그가 프로젝트의 저자임을 밝히지 않았기 때문에 거기에 있다고 생각합니다
jreptak
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.