redux-saga実現と原理


redux-sagaを紹介します.アプリを使って紹介している文章が多いですが、原理を紹介するのは本当に少ないです.自分の考えでredux-sagaの実行過程を説明します.ソースは多く削除されています.興味のあるものは自分で調べられます.
1.react-sagaとは何ですか?
redux-sagaは管理用です. Side Effects(非同期操作/副作用)のredux中間部品
2.何がレdux中間部品ですか?
redux中間部品はstore.dispatchを変えてdispatchを使用した時に副作用を処理できます.ここでは非同期動作を処理するために使用します.具体的には参考になるhttp://www.ruanyifeng.com/blo....中間の事柄に慣れていないなら、ぜひこの文章を読んでから続けてください.
3.レdux-sagaの特徴
  • 集中処理redux副作用問題
  • 非同期は、GEnerator
  • として実現される.
  • watch/worker(モニター-実行)の作業形態
  • redux-sagaは主にsagasモードを参考にして、generatorsを使って実現しました.
    まず、sagasモードについて説明します.長時間運行している事務によるシステムの運行効率と同時能力の問題を解決し、業務を複数の独立した事務に分けて、各業務は修正事務(ロールバック)を確保しています.もし業務過程が間違った状況に遭遇し、継続できない場合、修正事務を実行して完成したステップを修正できます.このようにして最終的な一致を保証します.栗を挙げます.A事務はB事務が完成してからでないと実行できません.B事務が長くかかりますと、A事務は長い間待たなければなりません.sagasモードを使えば、A事務とB事務を別々に実行できます.B事務が失敗すれば、A事務をロールバックします.
    sagasモードはドキュメントを参照します.https://blog.csdn.net/ethanwh...
    redux-sagaの中でsagasに対する参考:redux-sagaの中で、一つのsagaは一つのジェネレータ関数で、システム内で無期限に運行することができます.特定のactionがdispatchによって呼び覚まされる.sagaは、dispatchの追加的なactionsを継続してもよく、プログラムの単一状態ツリーにアクセスしてもよい.メインアプリケーションから起動し、一時停止とキャンセルもできます.sagasの特別な条件を満たす長い仕事を参考にして、ロールバックできます.
    次に、GEnerator Generator関数はES 6が提供する非同期プログラミングソリューションである.Generator関数を実行するとエルゴードオブジェクトに戻ります.つまり、Generator関数はステータスマシン以外のエルゴードオブジェクト生成関数です.返したエルゴードオブジェクトは、Generator関数内部の各状態を順次巡回することができます.形式的には,Generator関数は一般関数であるが,二つの特徴がある.一つは、functionキーワードと関数名の間に星番号があります.第二に、関数の内部はyield表現を使って、異なる内部状態を定義します.便宜上、以下のgenはgeneratorの略称である.
    簡単な例を挙げる
    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    
    var hw = helloWorldGenerator();
    実行結果
    hw.next()
    // { value: 'hello', done: false }
    
    hw.next()
    // { value: 'world', done: false }
    
    hw.next()
    // { value: 'ending', done: true }
    
    hw.next()
    // { value: undefined, done: true }
    もう少し複雑な伝値の例をください.
    function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
    }
    var b = foo(5);
    実行結果
    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...ここが重要です.ご了承ください.
    上の基礎を理解してから、redux-sagaの実行過程について説明します.
    私たちはデモの形式で分析します.いくつかの問題を解決する必要があります.
  • どうやって傍受執行を行いますか?
  • はどのように循環して各ステップを実行しますか?
  • 次のタスクはどう処理しますか?
  • ボタンをクリックするとバックエンド要求インターフェースから戻りデータをページに更新します.
    まず自分で中間部品を実現してこの需要を解決します.
    import axios from 'axios';
    
    const takers = []; //  generator
    
    //   gen
    function runGen(dispatch, gen) {
        //       
        if (!takers.length) {
            return;
        }
        //     generator
        takers.forEach((taker, key) => {
            const g = taker();
            //       taker
            takers.splice(key, 1);
            //   yield axios.post     promise
            const userPromise = g.next().value;
            userPromise.then((user) => {
                //  {dispatch, user}          ,   yield dispatch
                g.next({ dispatch, user });
                //   yield takers
                g.next()  
            });
        })
    }
    
    export default function fetchMiddleware(_ref2) {
        var getState = _ref2.getState,
            dispatch = _ref2.dispatch;
        //       taker, generator   takers,  dispatch   
        fetchMiddleware.run = () => takers.push(gen)
        //   dispatch
        return (next) => {
            return (action) => {
                // dispatch     
                var result = next(action);
                runGen(dispatch, gen)
                return result;
            };
        };
        return fetchMiddleware;
    }
    どう使いますか
    import fetchMiddleware from './fetchMiddleware';
    //    
    fetchMiddleware.run()
    //      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)
    }
    今からこの中間部品の実現手順を見てみましょう.
    ボタンをクリックしてdispatchを実行します.
    上記の例に対して、私達はgen.next()で一歩ずつ実行します.
  • 初期化時にgenをtakersに追加します.このようにする目的はボタンをクリックする時にgenerator
  • を実行することができます.
  • ボタンをクリックした時に、GEneratorを取得して、takersから削除して、dispatchを防止します.つまりアメリカを更新する時に、再度ジェネレータを実行して、循環呼び出し
  • をもたらします.
  • gen.next()つまり、yield axios.post('xxx')を実行して、ここで戻ってくるのはpromise
  • です.
  • promise.thenでgen.nextを呼び出し、実際にはyield dispatchを呼び出します.
  • 最後にgen.nextを呼び出し、実際にはyield tarss.pushを呼び出します.ここはゲナートをタタリに追加するために、次の前のステップの時に使います.
  • ここでは簡単にこの需要を実現しました.redux-sagaはもっと強力なappiを提供しました.次にredux-sagaはどうやって実現されたのか見てみましょう.
    まずコードの中でどうやってredux-sagaを使いますか?
    ./index.js
    import { put, call, take } from 'redux-saga/effects';
    import { createStore, applyMiddleware } from 'redux';
    import createSagaMiddleware from 'redux-saga';
    
    //   action
    const action = type => store.dispatch({ type })
    //   redux-saga   
    const sagaMiddleware = createSagaMiddleware()
    //   store
    const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))
    //   redux-saga   
    sagaMiddleware.run(rootSaga)
    
    function render() {
        ReactDOM.render(
             action('FETCH_USER')}
            />,
            document.getElementById('root'),
        )
    }
    ./saga.js
    //   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の中間部品を導入し、実はreduxの中間部品であり、dispatch(action)を変更することによって、actionを開始する時に非同期動作を処理させる.
  •   function sagaMiddleware(_ref2) {
        var getState = _ref2.getState,
            dispatch = _ref2.dispatch;
    
        // next = store.dispatch  redux     
        return function (next) {
          return function (action) {
            // dispatch(action) dispatch        
            var result = next(action); // hit reducers
            // emitter        ,subscribe  /emit  
            sagaEmitter.emit(action);
            return result;
          };
        };
      }
    //    
    export function emitter() {
      var subscribers = [];
    
      //          ,            
      function subscribe(sub) {
        subscribers.push(sub);
        return function () {
          return remove(subscribers, sub);
        };
      }
    
      //           ,          ,  dispatch             
      function emit(item) {
        var arr = subscribers.slice();
        for (var i = 0, len = arr.length; i < len; i++) {
          arr[i](item);
        }
      }
    }
    dispatchの時に購読器の関数を実行しますが、購読器の中の関数は何ですか?私達は第二歩を見ます.
  • 初期化時に一度だけサガを呼び出します.これは一回だけです.sagaを呼び出して、パイプラインのtakerを取得する方法を購読関数にプッシュして、taskを取得します.Taskインターフェースは、fork、middleare.runまたはrunSagaを介してSagaを実行する結果を指定しています.
  • 方法
    戻り値
    task.is Running()
    タスクがまだ戻っていない場合、またはエラーを投げた場合はtrueとなります.
    task.isCancelled()
    ジョブがキャンセルされたらtrueです.
    task.result()
    タスクの戻り値.タスクがまだ実行中の場合はundefinedです.
    task.error()
    ミッション投げのエラー.タスクが実行中である場合はundefinedである.
    task.done
    一つのPromiseは、次の二つのうちの一つである.1.任務の戻り値である.2.revoveは任務で投げ出したエラーrejectである.
    task.cel()
    ジョブをキャンセルします.(まだ実行中の場合)
    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;
    }
    次に、プロシージャで何が起こったかを見てみます.上のiteratorはroot Sagaというgenerator関数のパッケージです.procでredux-sagaはgen.nextを実行します.私たちのyield take('FETCHuUSER');その後、value={take:{pattern:"FETCHuUSER"}に戻ります.戻り値によって、runTake Effect()関数が実行されると判断します.この関数では、takeを登録して、nextをパイプのtakeに追加します.ここで呼び出しを終了し、taskに戻ります.
    export default function proc(iterator) {
      //            taker   (chan.put)push subscribers,                     chan.put(input)
      var stdChannel = _stdChannel(subscribe);
      //    task             
      var task = newTask(parentEffectId, name, iterator, cont);
    
      next();
    
      function next(arg, isErr) {
          var result = void 0;
          // iterator     rootSaga,       yield take('FETCH_USER');
          //   value={TAKE:{pattern: "FETCH_USER"}}
          result = iterator.next(arg);
          //      value,     
          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);
        };
        //      taker, next    takers   
        channel.take(takeCb, matcher(pattern));
      }
  • ユーザ情報を取得するボタンをクリックします.私たちはsagaミドルウェアに加入していますので、store.dispatchを開始します.
    次はsaga中間部品の主要コードです.
        // next = store.dispatch  redux     
        return function (next) {
          return function (action) {
            // dispatch(action)
            var result = next(action); // hit reducers
            //         
            sagaEmitter.emit(action);
            return result;
          };
        };
    次はsagaEmitter.emiit(action)が何をするか見てみましょう.
    //    
    export function emitter() {
        ...
      //         
      function emit(item) {
        var arr = subscribers.slice();
        for (var i = 0, len = arr.length; i < len; i++) {
          arr[i](item);
        }
      }
    }
    ここでは、購読関数を巡回して、購読関数の方法を実行します.初期化されたのは、私たちはすでにパイプラインの中でtakerを取得する方法を購読関数にプッシュしました.
    だから、ここで実行しているのはパイプの中のtakeを取得することです.
      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); //         taker
            return cb(input); //   cb    next
          }
        }
      }
    初期化実行中のyield taneでは、すでにgen.nextをtakersに入れています.ここでcbは実際にgen.nextを実行しています.初期化時にはgen.next関数は一回のgen.nextを実行しています.http://rest.learncode.academy...と同時に{type:FETCHuUSER}は前のステップの値として伝えられます.yeild callを実行してvalue{CALL:{args:[url]}に戻ります.戻り値によって、
    ここでソースを実行します.
      function runCallEffect(_ref4, effectId, cb) {
        const result = fn.apply(context, args);
        //        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);
        });
      }
    続いてgen.nextが実行したのはyield put({type:''UPDATEuUSER'、payload:data}で、実行したリターン値value{PUT:{action:{payload:}、type:[UPDATEuUSER])は、戻り値によって、
    ここでソースを実行します.
      function runPutEffect(_ref3, cb) {
        var channel = _ref3.channel,
            action = _ref3.action;
    
        asap(function () {
          var result = (channel ? channel.put : dispatch)(action); //             ,     dispatch(action)
    
          return cb(result);
        });
      }
    dispatch(action)を実行すると、ここで中間部品に戻り、再び第三段階の開始過程に入ります.そして更新が完了しました.今回はtarsを遍歴したところに行きます.tarsはすでに空の配列になりました.直接returnします.これで全体の取得インターフェースが更新されました.while(true)サイクルのため、再びyield tane Tale(「FETCHuUSER」)を実行します.
    以下に実行フローチャートを2枚添付します.
  • サガ初期化
  • dispatch
  • ここでは実行の流れといくつかのアプリだけを説明します.もっと多いのはドキュメントを参照してください.https://redux-saga-in-chinese...
    本論文では、いくつかのeffect方法を簡単に実現し、redux-sagaの原理を紹介します.本当にredux-sagaのすべての機能を実現するには、もう少し詳細を追加するだけでいいです.
    上记のソースはすべて削除版で、自分でソースコードを调べられます.