亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

redux-saga實(shí)現(xiàn)與原理

itvincent / 3598人閱讀

摘要:特點(diǎn)集中處理副作用問(wèn)題異步實(shí)現(xiàn)為監(jiān)聽(tīng)執(zhí)行的工作形式主要是借鑒模式和使用進(jìn)行實(shí)現(xiàn)的。返回的遍歷器對(duì)象,可以依次遍歷函數(shù)內(nèi)部的每一個(gè)狀態(tài)。為了方便,下文中是的簡(jiǎn)稱(chēng)。若任務(wù)仍在運(yùn)行中則為任務(wù)拋出的錯(cuò)誤。由于循環(huán),再次執(zhí)行。

介紹redux-saga使用和常用api介紹的文章很多,但是真正介紹原理的卻很少,下面我用自己的思路講一下redux-saga的執(zhí)行過(guò)程。源碼有很多刪減,感興趣的可自行查看。

1.react-saga是什么

redux-saga是用于管理?Side Effects(異步操作/副作用)的redux中間件

2.什么是redux中間件

redux中間件是通過(guò)改變store.dispatch使dispatch(action)時(shí)能處理副作用。在這里我們用于處理異步操作。
具體可參考http://www.ruanyifeng.com/blo...。如果對(duì)中間件不熟悉,請(qǐng)一定把這篇文章看完再往下進(jìn)行。

3.redux-saga特點(diǎn)

集中處理redux副作用問(wèn)題

異步實(shí)現(xiàn)為generator

watch/worker(監(jiān)聽(tīng)->執(zhí)行)的工作形式

redux-saga主要是借鑒 sagas模式 和使用 generators 進(jìn)行實(shí)現(xiàn)的。

首先,我們講講sagas模式:解決長(zhǎng)時(shí)間運(yùn)行的事務(wù)導(dǎo)致的系統(tǒng)運(yùn)行效率以及并發(fā)能力的問(wèn)題,將業(yè)務(wù)分為多個(gè)獨(dú)立的事務(wù),每個(gè)業(yè)務(wù)都會(huì)確保擁有修正事務(wù)(回滾),如果業(yè)務(wù)過(guò)程遇到了錯(cuò)誤的情況并且無(wú)法繼續(xù),它就可以執(zhí)行修正事務(wù)來(lái)修正已經(jīng)完成的步驟,這樣以保證最終的一致性。舉個(gè)栗子:A事務(wù)需要等待B事務(wù)完成之后才能執(zhí)行,如果B事務(wù)需要的時(shí)間很長(zhǎng),那么A事務(wù)就要等很久才能執(zhí)行,如果用sagas模式,可以把A事務(wù)和B事務(wù)分別執(zhí)行,如果B事務(wù)執(zhí)行失敗,則把A事務(wù)進(jìn)行回滾。

sagas模式參考文檔:https://blog.csdn.net/ethanwh...

redux-saga中對(duì)sagas的借鑒:在redux-saga里,一個(gè)saga就是一個(gè)生成器函數(shù)(generator function),可以在系統(tǒng)內(nèi)無(wú)限期運(yùn)行。當(dāng)特定action被dispatch時(shí),saga就可以被喚醒。saga也可以繼續(xù)dispatch額外的actions,也可以接入程序的單一狀態(tài)樹(shù)。也能從主應(yīng)用程序啟動(dòng),暫停和取消。借鑒了sagas的滿(mǎn)足特殊條件的長(zhǎng)事務(wù),可回滾。

接下來(lái)講講generator
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案。執(zhí)行 Generator 函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象,也就是說(shuō),Generator 函數(shù)除了狀態(tài)機(jī),還是一個(gè)遍歷器對(duì)象生成函數(shù)。返回的遍歷器對(duì)象,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個(gè)狀態(tài)。形式上,Generator 函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。一是,function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);二是,函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)(yield在英語(yǔ)里的意思就是“產(chǎn)出”)。為了方便,下文中g(shù)en是generator的簡(jiǎn)稱(chēng)。

舉個(gè)簡(jiǎn)單的例子

function* helloWorldGenerator() {
  yield "hello";
  yield "world";
  return "ending";
}

var hw = helloWorldGenerator();

執(zhí)行結(jié)果

hw.next()
// { value: "hello", done: false }

hw.next()
// { value: "world", done: false }

hw.next()
// { value: "ending", done: true }

hw.next()
// { value: undefined, done: true }

再來(lái)一個(gè)復(fù)雜一點(diǎn)的傳值的例子

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}
var b = foo(5);

執(zhí)行結(jié)果

b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

具體可參考文檔http://es6.ruanyifeng.com/#do...,這里很重要,請(qǐng)了解一下。

了解完上面的基礎(chǔ),下面我們開(kāi)始講redux-saga的執(zhí)行過(guò)程。

我們以一個(gè)demo的形式進(jìn)行分析。
需要解決幾個(gè)問(wèn)題:

怎么執(zhí)行監(jiān)聽(tīng)執(zhí)行?

如何循環(huán)把每一步執(zhí)行完?

怎么處理下一次任務(wù)?

當(dāng)我們點(diǎn)擊按鈕時(shí),會(huì)從后端請(qǐng)求接口,將返回的數(shù)據(jù)更新到頁(yè)面上

我們先自己實(shí)現(xiàn)一個(gè)中間件解決這個(gè)需求:

import axios from "axios";

const takers = []; // 存generator

// 執(zhí)行g(shù)en
function runGen(dispatch, gen) {
    // 防止無(wú)限循環(huán)
    if (!takers.length) {
        return;
    }
    // 遍歷執(zhí)行g(shù)enerator
    takers.forEach((taker, key) => {
        const g = taker();
        // 刪除已執(zhí)行的taker
        takers.splice(key, 1);
        // 執(zhí)行yield axios.post并返回一個(gè)promise
        const userPromise = g.next().value;
        userPromise.then((user) => {
            // 把{dispatch, user}設(shè)置為上一步的返回值,并執(zhí)行yield dispatch
            g.next({ dispatch, user });
            // 執(zhí)行yield takers
            g.next()  
        });
    })
}

export default function fetchMiddleware(_ref2) {
    var getState = _ref2.getState,
        dispatch = _ref2.dispatch;
    // 初始化時(shí)注冊(cè)taker,把generator添加到takers,用于dispatch時(shí)執(zhí)行
    fetchMiddleware.run = () => takers.push(gen)
    // 改變dispatch
    return (next) => {
        return (action) => {
            // dispatch時(shí)執(zhí)行這里
            var result = next(action);
            runGen(dispatch, gen)
            return result;
        };
    };
    return fetchMiddleware;
}

怎么用呢

import fetchMiddleware from "./fetchMiddleware";
// 初始化
fetchMiddleware.run()
// 需要執(zhí)行的gen
function* gen(){
    const { dispatch, user } = yield axios.post("http://rest.learncode.academy/api/wetern/users");
    const { data } = user;
    yield dispatch({ type: "UPDATE_USER", payload: data })
    yield takers.push(gen)
}

現(xiàn)在我們來(lái)看看這個(gè)中間件實(shí)現(xiàn)步驟

點(diǎn)擊按鈕,執(zhí)行dispatch({ type: "FEATCH_USER })

對(duì)于上面那個(gè)例子,我們用gen.next()實(shí)現(xiàn)一步一步執(zhí)行。

初始化時(shí)把gen添加到takers中,這樣做的目的是點(diǎn)擊按鈕的時(shí)候可以執(zhí)行g(shù)enerator

點(diǎn)擊按鈕的時(shí)候,獲取generator,然后從takers中刪除,防止dispatch({ type: "UPDATE_USER", payload: data })也就是更新user的時(shí)候再次執(zhí)行g(shù)enerator,造成循環(huán)調(diào)用

gen.next()也就是執(zhí)行yield axios.post("xxx"),這里返回的是一個(gè)promise

在promise.then中調(diào)用gen.next({ dispatch, user }),實(shí)際上是調(diào)用yield dispatch({ type: "UPDATE_USER", payload: data })

最后調(diào)用gen.next,實(shí)際上調(diào)用yield takers.push(gen)。這里是為了把generator添加到takers中,用于下一次上面第二步的時(shí)候用。

這里我們簡(jiǎn)單的實(shí)現(xiàn)了這個(gè)需求,redux-saga提供了更多更強(qiáng)大的api,下面我們看看redux-saga是怎么實(shí)現(xiàn)的吧。

先來(lái)看我們代碼中如何使用redux-saga吧

./index.js
import { put, call, take } from "redux-saga/effects";
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";

// 創(chuàng)建action
const action = type => store.dispatch({ type })
// 創(chuàng)建redux-saga中間件
const sagaMiddleware = createSagaMiddleware()
// 生成store
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))
// 執(zhí)行redux-saga中間件
sagaMiddleware.run(rootSaga)

function render() {
    ReactDOM.render(
         action("FETCH_USER")}
        />,
        document.getElementById("root"),
    )
}
./saga.js
// 創(chuàng)建saga
export function* rootSaga() {
    while(true) {
        yield take("FETCH_USER");
        const { data } =  yield call(axios.post, "http://rest.learncode.academy/api/wetern/users");
        yield put({ type: "UPDATE_USER", payload: data })
    }
}

首先引入redux-saga中間件,其實(shí)它就是redux的一個(gè)中間件,通過(guò)改變dispatch(action),使我們?cè)诎l(fā)起action的時(shí)候處理異步操作。

  function sagaMiddleware(_ref2) {
    var getState = _ref2.getState,
        dispatch = _ref2.dispatch;

    // next = store.dispatch 從redux中間件得出
    return function (next) {
      return function (action) {
        // dispatch(action) dispatch的時(shí)候會(huì)到這一步
        var result = next(action); // hit reducers
        // emitter 簡(jiǎn)單的事件對(duì)象,subscribe訂閱/emit觸發(fā)
        sagaEmitter.emit(action);
        return result;
      };
    };
  }
// 發(fā)射器
export function emitter() {
  var subscribers = [];

  // 存儲(chǔ)需要訂閱的方法,并返回一個(gè)取消訂閱的方法
  function subscribe(sub) {
    subscribers.push(sub);
    return function () {
      return remove(subscribers, sub);
    };
  }

  // 這里執(zhí)行所有訂閱方法,我們點(diǎn)擊頁(yè)面上的按鈕,執(zhí)行dispatch的時(shí)候會(huì)執(zhí)行訂閱器里的函數(shù)
  function emit(item) {
    var arr = subscribers.slice();
    for (var i = 0, len = arr.length; i < len; i++) {
      arr[i](item);
    }
  }
}

dispatch的時(shí)候會(huì)執(zhí)行訂閱器里的函數(shù),那么訂閱器里的函數(shù)是什么呢,我們接著看第二步

初始化的時(shí)候調(diào)用一次saga,這個(gè)只調(diào)用一次。調(diào)用saga的目的把獲取管道中taker的方法push到訂閱函數(shù)中,同時(shí)獲得一個(gè)task。Task 接口指定了通過(guò) fork,middleare.run 或 runSaga 運(yùn)行 Saga 的結(jié)果。

方法 返回值
task.isRunning() 若任務(wù)還未返回或拋出了一個(gè)錯(cuò)誤則為 true
task.isCancelled() 若任務(wù)已被取消則為 true
task.result() 任務(wù)的返回值。若任務(wù)仍在運(yùn)行中則為 undefined
task.error() 任務(wù)拋出的錯(cuò)誤。若任務(wù)仍在執(zhí)行中則為 undefined
task.done 一個(gè) Promise,以下二者之一:1.以任務(wù)的返回值 2.resolve以任務(wù)拋出的錯(cuò)誤 reject
task.cancel() 取消任務(wù)(如果任務(wù)仍在執(zhí)行中)
export function runSaga(storeInterface, saga) {
  // iterator是封裝后的rootSaga
  var task = proc(iterator, subscribe, wrapSagaDispatch(dispatch), getState, context,
   { sagaMonitor: sagaMonitor, logger: logger, onError: onError }, effectId, saga.name);

  return task;
}

下面看看執(zhí)行proc中發(fā)生了什么
上面的iterator是對(duì)rootSaga這個(gè)generator函數(shù)的封裝,在proc里redux-saga會(huì)執(zhí)行g(shù)en.next,就會(huì)執(zhí)行到我們的yield take("FETCH_USER");然后會(huì)返回value={TAKE:{pattern: "FETCH_USER"}},根據(jù)返回的值,判斷會(huì)執(zhí)行runTakeEffect()函數(shù),在這個(gè)函數(shù)里,會(huì)注冊(cè)一個(gè)taker,并把next添加到管道的taker中,到這里就結(jié)束了調(diào)用,并返回一個(gè)task。

export default function proc(iterator) {
  // 執(zhí)行這里會(huì)把獲取管道中taker的方法(chan.put)push到subscribers,所以上面第一步執(zhí)行訂閱中的方法實(shí)際上是執(zhí)行chan.put(input)
  var stdChannel = _stdChannel(subscribe);
  // 這里的task在第一次執(zhí)行的時(shí)候直接返回
  var task = newTask(parentEffectId, name, iterator, cont);

  next();

  function next(arg, isErr) {
      var result = void 0;
      // iterator是封裝后的rootSaga,這里執(zhí)行到的是yield take("FETCH_USER");
      // 返回value={TAKE:{pattern: "FETCH_USER"}}
      result = iterator.next(arg);
      // 根據(jù)返回的value,這里會(huì)執(zhí)行
      runEffect(result.value, parentEffectId, "", next);
  }
}
  // 這里cb是next
  function runTakeEffect(_ref2, cb) {
    var channel = _ref2.channel,
        pattern = _ref2.pattern,
        maybe = _ref2.maybe;

    channel = channel || stdChannel;
    var takeCb = function takeCb(inp) {
      return cb(inp);
    };
    // 給管道注冊(cè)taker,把next函數(shù)放到takers數(shù)組中
    channel.take(takeCb, matcher(pattern));
  }

點(diǎn)擊獲取用戶(hù)信息的按鈕(onFetchUser={() => action("FETCH_USER")}),因?yàn)槲覀兗尤肓藄aga中間件,讓我們發(fā)起store.dispatch({ type: FETCH_USER })的時(shí)候會(huì)處理異步操作,

下面是saga中間件的主要代碼

    // next = store.dispatch 從redux中間件得出
    return function (next) {
      return function (action) {
        // dispatch(action)
        var result = next(action); // hit reducers
        // 發(fā)射所有訂閱方法
        sagaEmitter.emit(action);
        return result;
      };
    };

下面我們來(lái)看看sagaEmitter.emit(action)會(huì)做什么

// 發(fā)射器
export function emitter() {
    ...
  // 發(fā)射所有訂閱方法
  function emit(item) {
    var arr = subscribers.slice();
    for (var i = 0, len = arr.length; i < len; i++) {
      arr[i](item);
    }
  }
}

這里會(huì)遍歷訂閱函數(shù),并執(zhí)行訂閱函數(shù)里的方法。在初始化的是我們已經(jīng)把獲取管道中taker的方法push到訂閱函數(shù)了,

所以我們這里執(zhí)行的是獲取管道中的taker。

  function put(input) {
      ...
    for (var i = 0; i < takers.length; i++) {
      var cb = takers[i];
      if (!cb[MATCH] || cb[MATCH](input)) {
        takers.splice(i, 1); // 刪除已經(jīng)執(zhí)行過(guò)的taker
        return cb(input); // 這里cb實(shí)際上是next
      }
    }
  }

在初始化執(zhí)行yield take("FETCH_USER")的時(shí)候,已經(jīng)把gen.next放入到takers中,這里cb(input)實(shí)際上是執(zhí)行g(shù)en.next({ type: FETCH_USER }),
因?yàn)樵诔跏蓟臅r(shí)候gen函數(shù)已經(jīng)執(zhí)行了一次gen.next,現(xiàn)在執(zhí)行g(shù)en.next則為const { data } = yield call(axios.post, "http://rest.learncode.academy..."),同時(shí)把{ type: FETCH_USER }作為上一步的值傳入。執(zhí)行yeild call返回value
{CALL:{args: [url]}},根據(jù)返回值,

這里會(huì)執(zhí)行源碼中的

  function runCallEffect(_ref4, effectId, cb) {
    const result = fn.apply(context, args);
    // 這里執(zhí)行結(jié)果是promise
    return resolvePromise(result, cb);
  }
  function resolvePromise(promise, cb) {
    // cb是gen.next,這里把yield call的返回值傳遞給gen.next
    promise.then(cb, function (error) {
      return cb(error, true);
    });
  }

接下來(lái)gen.next執(zhí)行到的是yield put({ type: "UPDATE_USER", payload: data }),執(zhí)行的返回值value
{PUT:{action:{payload:{id: "xx",type:"UPDATE_USER"}}}},根據(jù)返回值,

這里會(huì)執(zhí)行源碼中的

  function runPutEffect(_ref3, cb) {
    var channel = _ref3.channel,
        action = _ref3.action;

    asap(function () {
      var result = (channel ? channel.put : dispatch)(action); // 實(shí)際上我們演示的這段代碼,這里會(huì)執(zhí)行dispatch(action)

      return cb(result);
    });
  }

執(zhí)行dispatch(action)這里又會(huì)回到中間件中再次進(jìn)入第三步的開(kāi)始過(guò)程。并完成更新。
這次執(zhí)行到遍歷takers的地方,takers已經(jīng)為空數(shù)組,會(huì)直接return,至此完成了整個(gè)獲取接口到更新數(shù)據(jù)。
由于while(true)循環(huán),再次執(zhí)行yield take("FETCH_USER")。

下面附上兩張執(zhí)行流程圖

saga初始化

dispatch

這里只解釋了執(zhí)行流程和幾個(gè)api,更多的請(qǐng)參考文檔https://redux-saga-in-chinese...

本文通過(guò)簡(jiǎn)單實(shí)現(xiàn)了幾個(gè)effect方法來(lái)地介紹了redux-saga的原理,要真正做到redux-saga的所有功能,只需要再添加一些細(xì)節(jié)就可以了

注:上面的源碼均為刪減版,可自行查看源碼。個(gè)人文章,轉(zhuǎn)載請(qǐng)注明出處。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/98089.html

相關(guān)文章

  • 超級(jí)易懂的redux-saga原理解析

    摘要:原文地址前言筆者最近在做一些后臺(tái)項(xiàng)目,使用的是,其使用了處理異步數(shù)據(jù)流,本文將對(duì)的原理做一個(gè)簡(jiǎn)單的解讀,并將實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的。函數(shù)的自動(dòng)流程控制在中,是指一些長(zhǎng)時(shí)操作,用函數(shù)表示。 原文地址 前言 筆者最近在做一些后臺(tái)項(xiàng)目,使用的是Ant Design Pro,其使用了redux-saga處理異步數(shù)據(jù)流,本文將對(duì)redux-saga的原理做一個(gè)簡(jiǎn)單的解讀,并將實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的redux...

    wendux 評(píng)論0 收藏0
  • 精益 React 學(xué)習(xí)指南 (Lean React)- 3.4 掌控 redux 異步

    摘要:舉例來(lái)說(shuō)一個(gè)異步的請(qǐng)求場(chǎng)景,可以如下實(shí)現(xiàn)任何異步的邏輯都可以,如等等也可以使用的和。實(shí)際上在中,一個(gè)就是一個(gè)函數(shù)。 書(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
  • redux-saga框架使用詳解及Demo教程

    摘要:通過(guò)創(chuàng)建將所有的異步操作邏輯收集在一個(gè)地方集中處理,可以用來(lái)代替中間件。 redux-saga框架使用詳解及Demo教程 前面我們講解過(guò)redux框架和dva框架的基本使用,因?yàn)閐va框架中effects模塊設(shè)計(jì)到了redux-saga中的知識(shí)點(diǎn),可能有的同學(xué)們會(huì)用dva框架,但是對(duì)redux-saga又不是很熟悉,今天我們就來(lái)簡(jiǎn)單的講解下saga框架的主要API和如何配合redux框...

    Nosee 評(píng)論0 收藏0
  • React手稿之 React-Saga

    摘要:相當(dāng)于一個(gè)放置在與中的墊片。之所以稱(chēng)之謂副作用呢,就是為了不讓觸發(fā)一個(gè)時(shí),立即執(zhí)行。也就是在與之間做一個(gè)事情,比如異步獲取數(shù)據(jù)等。使用了中的功能,避免了像的回調(diào)地獄。把放入中最后再實(shí)現(xiàn)相就的即可在線示例推薦閱讀手稿 Redux-Saga redux-saga 是一個(gè)用于管理應(yīng)用程序副作用(例如異步獲取數(shù)據(jù),訪問(wèn)瀏覽器緩存等)的javascript庫(kù),它的目標(biāo)是讓副作用管理更容易,執(zhí)行更...

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

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

0條評(píng)論

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