国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

精益 React 學(xué)習(xí)指南 (Lean React)- 3.5 compose redux saga

Joyven / 2257人閱讀

摘要:通過可以實(shí)現(xiàn)很多有趣的簡潔的控制。這里默認(rèn)使用到了的一個(gè)特性,如果某一個(gè)任務(wù)成功了過后,其他任務(wù)都會(huì)被。組合是的內(nèi)關(guān)鍵字,使用的場(chǎng)景是一個(gè)。

書籍完整目錄

3.5 compose redux sages

基于 redux-thunk 的實(shí)現(xiàn)特性,可以做到基于 promise 和遞歸的組合編排,而 redux-saga 提供了更容易的更高級(jí)的組合編排方式(當(dāng)然這一切要?dú)w功于 Generator 特性),這一節(jié)的主要內(nèi)容為:

基于 take Effect 實(shí)現(xiàn)更自由的任務(wù)編排

fork 和 cancel 實(shí)現(xiàn)非阻塞任務(wù)

Parallel 和 Race 任務(wù)

saga 組合 yield* saga

channels

3.5.1 基于 take Effect 實(shí)現(xiàn)更自由的任務(wù)編排

前面我們使用過 takeEvery helper, 其實(shí)底層是通過 take effect 來實(shí)現(xiàn)的。通過 take effect 可以實(shí)現(xiàn)很多有趣的簡潔的控制。

如果用 takeEvery 實(shí)現(xiàn)日志打印,我們可以用:

import { takeEvery } from "redux-saga"
import { put, select } from "redux-saga/effects"

function* watchAndLog() {
  yield* takeEvery("*", function* logger(action) {
    const state = yield select()

    console.log("action", action)
    console.log("state after", state)
  })
}

使用使用 take 過后可以改為:

import { take } from "redux-saga/effects"
import { put, select } from "redux-saga/effects"

function* watchAndLog() {
  while (true) {
    const action = yield take("*")
    const state = yield select()

    console.log("action", action)
    console.log("state after", state)
  }
}

while(true) 的執(zhí)行并非是死循環(huán),而只是不斷的生成迭代項(xiàng)而已,take Effect 在沒有獲取到對(duì)象的 action 時(shí),會(huì)停止執(zhí)行,直到接收到 action 才會(huì)執(zhí)行后面的代碼,然后重新等待

take 和 takeEvery 最大的區(qū)別在于 take 是主動(dòng)獲取 action ,相當(dāng)于 action = getNextAction() , 而 takeEvery 是消息推送。

基于主動(dòng)獲取的,可以做到更自由的控制,如下面的兩個(gè)例子:

完成了三個(gè)任務(wù)后,提示恭喜

import { take, put } from "redux-saga/effects"
function* watchFirstThreeTodosCreation() {
  for (let i = 0; i < 3; i++) {
    const action = yield take("TODO_CREATED")
  }
  yield put({type: "SHOW_CONGRATULATION"})
}

登錄和登出邏輯可以放在同一個(gè)函數(shù)內(nèi)共享變量

function* loginFlow() {
  while (true) {
    yield take("LOGIN")
    // ... perform the login logic
    yield take("LOGOUT")
    // ... perform the logout logic
  }
}

take 最不可思議的地方就是,將 異步的任務(wù)用同步的方式來編排 ,使用好 take 能極大的簡化交互邏輯處理

3.5.2 fork 和 cancel 實(shí)現(xiàn)非阻塞任務(wù)

在提非阻塞之前肯定要先要說明什么叫阻塞的代碼。我們看一下下面的例子:

function* generatorFunction() {
  console.log("start")

  yield take("action1")
  console.log("take action1")

  yield call("api")
  console.log("call api")

  yield put({type: "SOME_ACTION"})
  console.log("put blabla")
}

因?yàn)?generator 的特性,必須要等到 take 完成才會(huì)輸出 take action1, 同理必須要等待 call api 完成才會(huì)輸出 call api, 這就是我們所說的阻塞。

那阻塞會(huì)造成什么問題呢?見下面的例子:

一個(gè)登錄的例子(這是一段有問題的代碼,可以先研究一下這段代碼問題出在哪兒)

import { take, call, put } from "redux-saga/effects"
import Api from "..."

function* authorize(user, password) {
  try {
    const token = yield call(Api.authorize, user, password)
    yield put({type: "LOGIN_SUCCESS", token})
    return token
  } catch(error) {
    yield put({type: "LOGIN_ERROR", error})
  }
}

function* loginFlow() {
  while (true) {
    const {user, password} = yield take("LOGIN_REQUEST")
    const token = yield call(authorize, user, password)
    if (token) {
      yield call(Api.storeItem, {token})
      yield take("LOGOUT")
      yield call(Api.clearItem, "token")
    }
  }
}

我們先來分析一下 loginFlow 的流程:

通過 take effect 監(jiān)聽 login_request action

通過 call effect 來異步獲取 token (call 不僅可以用來調(diào)用返回 Promise 的函數(shù),也可以用它來調(diào)用其他 Generator 函數(shù),返回結(jié)果為調(diào)用 Generator return 值)

成功(有 token)
1 過后異步存儲(chǔ) token

等待 logout action

logout 事件觸發(fā)后異步清除 token

然后回到第 0 步

失敗(token === undefined) 回到第 0 步

其中的問題:

一個(gè)隱藏的陷阱,在調(diào)用 authorize 的時(shí)候,如果用戶點(diǎn)擊了頁面中的 logout 按鈕將會(huì)沒有反應(yīng)(此時(shí)還沒有執(zhí)行 take("LOGOUT")) , 也就是被 authorize 阻塞了。

redux-sage 提供了一個(gè)叫 fork 的 Effect,可以實(shí)現(xiàn)非阻塞的方式,下面我們重新設(shè)計(jì)上面的登錄例子:

function* authorize(user, password) {
  try {
    const token = yield call(Api.authorize, user, password)
    yield put({type: "LOGIN_SUCCESS", token})
  } catch(error) {
    yield put({type: "LOGIN_ERROR", error})
  }
}

function* loginFlow() {
  while(true) {
    const {user, password} = yield take("LOGIN_REQUEST")
    yield fork(authorize, user, password)
    yield take(["LOGOUT", "LOGIN_ERROR"])
    yield call(Api.clearItem("token"))
  }
}

token 的獲取放在了 authorize saga 中,因?yàn)?fork 是非阻塞的,不會(huì)返回值

authorize 中的 call 和 loginFlow 中的 take 并行調(diào)用

這里 take 了兩個(gè) action , take 可以監(jiān)聽并發(fā)的 action ,不管哪個(gè) action 觸發(fā)都會(huì)執(zhí)行 call(Api.clearItem...) 并回到 while 開始

在用戶觸發(fā) logout 之前, 如果 authorize 成功,那么 loginFlow 會(huì)等待 LOGOUT action

在用戶觸發(fā) logout 之前, 如果 authorize 失敗,那么 loginFlow 會(huì) take("LOGIN_ERROR")

如果在用戶觸發(fā) logout 的時(shí)候,authorize 還沒有執(zhí)行完成,那么會(huì)執(zhí)行后面的語句并回到 while 開始

這個(gè)過程中的問題是如果用戶觸發(fā) logout 了,沒法停止 call api.authorize , 并會(huì)觸發(fā) LOGIN_SUCCESS 或者 LOGIN_ERROR action 。

redux-saga 提供了 cancel Effect,可以 cancel 一個(gè) fork task

import { take, put, call, fork, cancel } from "redux-saga/effects"

// ...

function* loginFlow() {
  while (true) {
    const {user, password} = yield take("LOGIN_REQUEST")
    // fork return a Task object
    const task = yield fork(authorize, user, password)
    const action = yield take(["LOGOUT", "LOGIN_ERROR"])
    if (action.type === "LOGOUT")
      yield cancel(task)
    yield call(Api.clearItem, "token")
  }
}

cancel 的了某個(gè) generator, generator 內(nèi)部會(huì) throw 一個(gè)錯(cuò)誤方便捕獲,generator 內(nèi)部 可以針對(duì)不同的錯(cuò)誤做不同的處理

import { isCancelError } from "redux-saga"
import { take, call, put } from "redux-saga/effects"
import Api from "..."

function* authorize(user, password) {
  try {
    const token = yield call(Api.authorize, user, password)
    yield put({type: "LOGIN_SUCCESS", token})
    return token
  } catch(error) {
    if(!isCancelError(error))
      yield put({type: "LOGIN_ERROR", error})
  }
}
3.5.3 Parallel 和 Race 任務(wù) Parallel

基于 generator 的特性,下面的代碼會(huì)按照順序執(zhí)行

const users  = yield call(fetch, "/users"),
      repos = yield call(fetch, "/repos")

為了優(yōu)化效率,可以讓兩個(gè)任務(wù)并行執(zhí)行

const [users, repos]  = yield [
  call(fetch, "/users"),
  call(fetch, "/repos")
]
Race

某些情況下可能會(huì)對(duì)優(yōu)先完成的任務(wù)進(jìn)行處理,一個(gè)很常見的例子就是超時(shí)處理,當(dāng)請(qǐng)求一個(gè) API 超過多少時(shí)間過后執(zhí)行特定的任務(wù)。

eg:

import { race, take, put } from "redux-saga/effects"
import { delay } from "redux-saga"

function* fetchPostsWithTimeout() {
  const {posts, timeout} = yield race({
    posts: call(fetchApi, "/posts"),
    timeout: call(delay, 1000)
  })

  if (posts)
    put({type: "POSTS_RECEIVED", posts})
  else
    put({type: "TIMEOUT_ERROR"})
}

這里默認(rèn)使用到了 race 的一個(gè)特性,如果某一個(gè)任務(wù)成功了過后,其他任務(wù)都會(huì)被 cancel 。

3.5.4 yield* 組合 saga

yield* 是 generator 的內(nèi)關(guān)鍵字,使用的場(chǎng)景是 yield 一個(gè) generaor。

yield* someGenerator 相當(dāng)于把 someGenerator 的代碼放在當(dāng)前函數(shù)執(zhí)行,利用這個(gè)特性,可以組合使用 saga

function* playLevelOne() { ... }
function* playLevelTwo() { ... }
function* playLevelThree() { ... }
function* game() {
  const score1 = yield* playLevelOne()
  put(showScore(score1))

  const score2 = yield* playLevelTwo()
  put(showScore(score2))

  const score3 = yield* playLevelThree()
  put(showScore(score3))
}
3.5.5 channels 通過 actionChannel 實(shí)現(xiàn)緩存區(qū)

先看如下的例子:

import { take, fork, ... } from "redux-saga/effects"

function* watchRequests() {
  while (true) {
    const {payload} = yield take("REQUEST")
    yield fork(handleRequest, payload)
  }
}

function* handleRequest(payload) { ... }

這個(gè)例子是典型的 watch -> fork ,也就是每一個(gè) REQEST 請(qǐng)求都會(huì)被并發(fā)的執(zhí)行,現(xiàn)在如果有需求要求 REQUEST 一次只能執(zhí)行一個(gè),這種情況下可以使用到 actionChannel

通過 actionChannel 修改上例子

import { take, actionChannel, call, ... } from "redux-saga/effects"

function* watchRequests() {
  //  為 REQUEST 創(chuàng)建一個(gè) actionChannel 相當(dāng)于一個(gè)緩沖區(qū)
  const requestChan = yield actionChannel("REQUEST")
  while (true) {
    // 重 channel 中取一個(gè) action
    const {payload} = yield take(requestChan)
    // 使用非阻塞的方式調(diào)用 request
    yield call(handleRequest, payload)
  }
}

function* handleRequest(payload) { ... }

channel 可以設(shè)置緩沖區(qū)的大小,如果只想處理最近的5個(gè) action 可以如下設(shè)置

import { buffers } from "redux-saga"
const requestChan = yield actionChannel("REQUEST", buffers.sliding(5))
eventChannel 和外部事件連接起來

eventChannel 不同于 actionChannel,actionChannel 是一個(gè) Effect ,而 eventChannel 是一個(gè)工廠函數(shù),可以創(chuàng)建一個(gè)自定義的 channel

下面創(chuàng)建一個(gè)倒計(jì)時(shí)的 channel 工廠

import { eventChannel, END } from "redux-saga"

function countdown(secs) {
  return eventChannel(emitter => {
      const iv = setInterval(() => {
        secs -= 1
        if (secs > 0) {
          emitter(secs)
        } else {
          // 結(jié)束 channel
          emitter(END)
          clearInterval(iv)
        }
      }, 1000);

      // 返回一個(gè) unsubscribe 方法
      return () => {
        clearInterval(iv)
      }
    }
  )
}

通過 call 使用創(chuàng)建 channel

export function* saga() {
  const chan = yield call(countdown, value)
  try {    
    while (true) {
      // take(END) 會(huì)導(dǎo)致直接跳轉(zhuǎn)到 finally
      let seconds = yield take(chan)
      console.log(`countdown: ${seconds}`)
    }
  } finally {
    // 支持外部 cancel saga
    if (yield cancelled()) {
      // 關(guān)閉 channel 
      chan.close()
      console.log("countdown cancelled")
    } else {
      console.log("countdown terminated")
    }
  }
}
通過 channel 在 saga 之間通信

除了 eventChannel 和 actionChannel,channel 可以不用連接任何事件源,直接創(chuàng)建一個(gè)空的 channel,然后手動(dòng)的 put 事件到 channel 中

以上面的 watch->fork 為基礎(chǔ),需求改為 ,需要同時(shí)并發(fā) 3 個(gè)request 請(qǐng)求執(zhí)行:

import { channel } from "redux-saga"
import { take, fork, ... } from "redux-saga/effects"

function* watchRequests() {
  // 創(chuàng)建一個(gè)空的 channel
  const chan = yield call(channel)
  // fork 3 個(gè) worker saga
  for (var i = 0; i < 3; i++) {
    yield fork(handleRequest, chan)
  }
  while (true) {
    // 等待 request action
    const {payload} = yield take("REQUEST")
    // put payload 到 channel 中
    yield put(chan, payload)
  }
}

function* handleRequest(chan) {
  while (true) {
    const payload = yield take(chan)
    // process the request
  }
}
參考鏈接

http://yelouafi.github.io/redux-saga/docs/advanced/index.html

http://gajus.com/blog/2/the-definitive-guide-to-the-javascript-generators#understanding-the-execution-flow

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/79753.html

相關(guān)文章

  • 精益 React 學(xué)習(xí)指南Lean React)- 3.4 掌控 redux 異步

    摘要:舉例來說一個(gè)異步的請(qǐng)求場(chǎng)景,可以如下實(shí)現(xiàn)任何異步的邏輯都可以,如等等也可以使用的和。實(shí)際上在中,一個(gè)就是一個(gè)函數(shù)。 書籍完整目錄 3.4 redux 異步 showImg(https://segmentfault.com/img/bVyou8); 在大多數(shù)的前端業(yè)務(wù)場(chǎng)景中,需要和后端產(chǎn)生異步交互,在本節(jié)中,將詳細(xì)講解 redux 中的異步方案以及一些異步第三方組件,內(nèi)容有: redu...

    JouyPub 評(píng)論0 收藏0
  • 精益 React 學(xué)習(xí)指南Lean React)- 3.3 理解 redux 中間件

    摘要:數(shù)組為新的數(shù)組,包含了方法將新的和結(jié)合起來,生成一個(gè)新的方法返回的新增了一個(gè)方法,這個(gè)新的方法是改裝過的,也就是封裝了中間件的執(zhí)行。 書籍完整目錄 3.3 理解 Redux 中間件 showImg(https://segmentfault.com/img/bVymkt); 這一小節(jié)會(huì)講解 redux 中間件的原理,為下一節(jié)講解 redux 異步 action 做鋪墊,主要內(nèi)容為: ...

    Kerr1Gan 評(píng)論0 收藏0
  • 精益 React 學(xué)習(xí)指南Lean React)- 4.2 react patterns

    摘要:另外一點(diǎn)是組件應(yīng)該盡量保證獨(dú)立性,避免和外部的耦合,使用全局事件造成了和外部事件的耦合。明確的職責(zé)分配也增加了應(yīng)用的確定性明確只有組件能夠知道狀態(tài)數(shù)據(jù),且是對(duì)應(yīng)部分的數(shù)據(jù)。 書籍完整目錄 4.2 react patterns 修改 Props Immutable data representation 確定性 在 getInitialState 中使用 props 私有狀態(tài)和...

    Berwin 評(píng)論0 收藏0
  • 精益 React 學(xué)習(xí)指南Lean React)- 1.1 React 介紹

    摘要:單向數(shù)據(jù)流應(yīng)用的核心設(shè)計(jì)模式,數(shù)據(jù)流向自頂向下我也是性子急的人,按照技術(shù)界的慣例,在學(xué)習(xí)一個(gè)技術(shù)前,首先得說一句。然而的單向數(shù)據(jù)流的設(shè)計(jì)讓前端定位變得簡單,頁面的和數(shù)據(jù)的對(duì)應(yīng)是唯一的我們可以通過定位數(shù)據(jù)變化就可以定位頁面展現(xiàn)問題。 書籍完整目錄 1.1 React 介紹 showImg(https://segmentfault.com/img/bVvJgS); 1.1.1 React ...

    lsxiao 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<