在React/Redux應用中使用Sagas管理異步操作(翻譯)

在React/Redux應用中使用Sagas管理異步操作

參考這篇文章,譯文可能先幼稚,參考看看吧.redux-saga本身的一些概念很難
可以也參考 redux-saga的文檔

Redux是一個和Flux類似的框架,在React社區(qū)中增長很快.他通過使用單個狀態(tài)的原子性和純函數(shù)式reduce來更新state,從而加強單向數(shù)據(jù)流,減小了數(shù)據(jù)操作的復雜性.
對于我,配置React+Flux一直是一根肉中刺,包括有Action creators的協(xié)作,異步操作也是非常的棘手.解決辦法是在React組件中使用生命周期方法(life cycle),例如componentDidupdate,componentWillUpdate等等,在action creators中通過返回thunks(類似Promise對象)對象也可以工作.但是這些方法似乎在有些條件下會不太好使用.
我了更好的表達我的意思,我們來看看一個簡單的Timer App. 整個APP的代碼可在這里.

計時器APP

這個app允許使用者開始和停止一個定時器,也可以重置它.

計時器-app

我們可以把這個app可以看做一個在stopped和Running兩個狀態(tài)之間相互轉變的有限狀態(tài)機(finite machine).參見下面的簡圖.當timer在Running狀態(tài)時,狀態(tài)機會每一秒種更新app一次.

狀態(tài)機

讓我首先把app的基本設置看一下,然后我們演示一下怎么在action creators和React組件之外使用sagas幫助管理異步操作(side-effects).

Actions

在模塊中有四個actions

  1. START-計時器改變?yōu)檫\行狀態(tài).
  2. TICK-時鐘每個滴答以后遞增定時器
  3. STOP-計時器改變?yōu)橥V範顟B(tài)
  4. RESET-復位定時器
  // actions.js,四種action

export default { start: () => ({ type: 'START' })
               , tick: () => ({ type: 'TICK' })
               , stop: () => ({ type: 'STOP' })
               , reset: () => ({ type: 'RESET' })
               };

狀態(tài)模型和Reducer

計時器的狀態(tài)由兩部分屬性組成:status和seconds

 type Model = {
  status: string;
  seconds: number;
 }

status是運行和停止兩個狀態(tài),seconds只要定時器開始計時就開始累積.

Reducer的實際代碼如下

  // reducer.js

const INITIAL_STATE = { status: 'Stopped'
                      , seconds: 0
                      };

export default (state = INITIAL_STATE, action = null) => {
  switch (action.type) {
    case 'START':
      return { ...state, status: 'Running' };
    case 'STOP':
      return { ...state, status: 'Stopped' };
    case 'TICK':
      return { ...state, seconds: state.seconds + 1 };
    case 'RESET':
      return { ...state, seconds: 0 };
    default:
      return state;
  }
};

Timer的UI視圖

視圖(view)是比較單純的的,所以和異步操作是完全隔絕的(side-effects free).視圖渲染當前的時間和狀態(tài).于此同時在用戶點擊Reset,Start或Stop按鈕的時候喚醒相應的回調(diào)函數(shù).

export const Timer = ({ start, stop, reset, state }) => (
  <div>
    <p>
      { getFormattedTime(state) } ({ state.status })
    </p>
    <button
      disabled={state.status === 'Running'}
      onClick={() => reset()}>
      Reset
    </button>
    <button
      disabled={state.status === 'Running'}
      onClick={() => start()}>
      Start
    </button>
    <button
      disabled={state.status === 'Stopped'}
      onClick={stop}>
      Stop
    </button>
  </div>
);

問題:怎么處理周期性的更新操作.

目前app的狀態(tài)是在運行和停止之間轉變,但是還沒有周期性改變定時器的機制.
在典型的Redux+React的app中,有兩種方法可以處理周期性的更新.

  1. 視圖周期性的回調(diào)action creator
  2. action creator返回一個thunk對象,這個對象周期性的dispatch TICK actions.
解決方案1:讓視圖dispatch更新

對于#1方案,視圖必須等待定時器的狀態(tài)從停止轉變?yōu)殚_始才能開始周期性的action派發(fā).意思是我們不得不使用有狀態(tài)的組件.

class Timer extends Component {
  componentWillReceiveProps(nextProps) {
    const { state: { status: currStatus } } = this.props;
    const { state: { status: nextStatus } } = nextProps;

    if (currState === 'Stopped' && nextState === 'Running') {
      this._startTimer();
    } else if (currState === 'Running' && nextState === 'Stopped') {
      this._stopTimer();
    }
  }

  _startTimer() {
    this._intervalId = setInterval(() => {
        this.props.tick();
    }, 1000);
  }

  _stopTimer() {
    clearInterval(this._intervalId);
  }

  // ...
}

這種處理方式可以工作,但是這會使視圖變得滿是狀態(tài),而且也會不純凈.另一個問題是我們的組件現(xiàn)在不僅僅需要渲染HTML,捕獲用戶的交互操作還要承擔更多的工作.這種方式里引入致異步操作會使視圖和應用作為一個整體,很難理清.在計時器這個app里面可能還不是什么問題.但是如果在一個大型的應用中,你可能想把異步操作放到整個應用的外面.

所以使用Thunks對象怎么樣?

解決方案2:在Action Creator中使用Thunks對象

替代方案1在視圖中進行操作,可以在我們的action creator中使用thunks.改變一下start的action creator

 export default {
  start: () => (
    (dispatch, getState) => {
      // This transitions state to Running
      dispatch({ type: 'START' });
      //上面的注釋的譯文:dispatch({type:'START'})改變狀態(tài)為Running

      // Check every 1 second if we are still Running.
      // If so, then dispatch a `TICK`, otherwise stop
      // the timer.
      //每一秒種檢測一下狀態(tài)是不是還是Running,如果是的
      //話,dispatch ‘TICK’aciton.否則就停止計時器
      const intervalId = setInterval(() => {
        const { status } = getState();

        if (status === 'Running') {
          dispatch({ type: 'TICK' });
        } else {
          clearInterval(intervalId);
        }
      }, 1000);
    }
  )
  // ...
};

Start action creator將會dispatch一個START action,只要start回調(diào)函數(shù)被調(diào)用.接著只要計時器只要還在工作,每一秒鐘將會dispatch一個TICK action.
在action creator中使用的方式一個問題是action creator現(xiàn)在要做很多的事情.測試也是一個很難完成的任務,因為沒有返回任何數(shù)據(jù).

最好的解決辦法是:使用Sagas去管理計時器.

redux-sagas重新定義side-effects為Effects.Effects由Sagas生成.sagas的概念據(jù)我所知來自CQRS和Event Sourcing世界.有許多討論爭論sagas到底是什么,但是你可以認為sagas是和系統(tǒng)交互的永久線程:

  1. 對系統(tǒng)中的acion dispach做出反應
  2. 往系統(tǒng)中Dispatch新的actions
  3. 可以使用內(nèi)部機制在沒有外部actions的情況下自我復蘇.例如周期性的蘇醒.

在redux-saga里,一個saga就是一個生成器函數(shù)(generator function),可以在系統(tǒng)內(nèi)無限期運行.當特定action被dispatch時,saga就可以被喚醒.saga也可以繼續(xù)dispatch額外的actions,也可以接入程序的單一狀態(tài)樹.
例如,我們想在計時器運行的時候,周期性的dispatch TICKS.看看下面的操作:

  function* runTimer(getState) {
  // The sagasMiddleware will start running this generator.
  //sagas中間件將開始運行這個生成器函數(shù).
  
  

  // Wake up when user starts timer.
  //當用戶開始計時器的時候喚醒.
  while(yield take('START')) {
    while(true) {
      // This side effect is not run yet, so it can be treated
      //side effect 沒有運行,所以可以看做數(shù)據(jù)
      // as data, making it easier to test if needed.
      //這樣測試比較容易一點
      yield call(wait, ONE_SECOND);

      // Check if the timer is still running.
      //檢測計時器是否運行
      // If so, then dispatch a TICK.
      //如果計時器運行的話,就dispatch一個TICK
      if (getState().status === 'Running') {
        yield put(actions.tick());
      // Otherwise, go idle until user starts the timer again.
      //如果計時器沒有運行的話,就進入休眠狀態(tài)等待計時器的重新工作
      } else {
        break;
      }
    }
  }
}

正如你所見到的,一個saga使用普通的JavaScript控制流程來構建協(xié)作side-effects和action creators的過程.take函數(shù)在START action被dispatch的時候喚醒.call函數(shù)允許我們創(chuàng)建類似于待辦事項的等待效果.(就是類似list-todo,已經(jīng)在日程表中列出,但還沒有執(zhí)行的任務)
通過使用saga,我們可以保持視圖和action creator成為純函數(shù).saga使我們可以使用類似javascript構造函數(shù)的方式創(chuàng)建state轉變的模型.

包裝

Sagas是系統(tǒng)內(nèi)管理side-effects的途徑.當你的應用中需要長時間運行的進程來協(xié)作多個action creators和side-effects的時候,Sagas將會非常的合適.
Sagas不僅對actions做出響應,而且對內(nèi)部機制也可以做出響應(例如,時間依賴的effects).Sagas尤其有用,特別是你需要在正常的Flux流程之外管理side-effects的時候.例如,一個用戶的交互操作可能會有更多的action產(chǎn)生,但是這些actions卻不需要用戶更多的操作.
最后,當你需要一個無限狀態(tài)機模型的時候,sagas也值得一試.

如果你想看看Timer app的完整代碼,看看這里.

你準備嘗試sagas了嗎?好了,有什么想法呢?

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容