资讯专栏INFORMATION COLUMN

React生态,dva源码阅读

bergwhite / 3244人阅读

摘要:下面会从浅到深,淡淡在阅读源码过程中自己的理解。分拆子页面后,每一个子页面对应一个文件。总结上面就是最早版本的源码,很简洁的使用了等其目的也很简单简化相关生态的繁琐逻辑参考源码地址

  dva的思想还是很不错的,大大提升了开发效率,dva集成了Redux以及Redux的中间件Redux-saga,以及React-router等等。得益于Redux的状态管理,以及Redux-saga中通过Task和Effect来处理异步的概念,dva在这些工具的基础上高度封装,只暴露出几个简单的API就可以设计数据模型。

  最近看了一下Redux-saga的源码,结合以及之前在项目中一直采用的是redux-dark模式来将reducers和sagas(generator函数处理异步)拆分到不同的子页面,每一个页面中同一个文件中包含了该页面状态的reducer和saga,这种简单的封装已经可以大大的提升项目的可读性。

  最近看了dva源码,熟悉了dva是在上层如何做封装的。下面会从浅到深,淡淡在阅读dva源码过程中自己的理解。

redux-dark模式

dva 0.0.12版本的使用和源码理解

本文的原文地址为: https://github.com/forthealll...
欢迎star

一、redux-dark模式

  在使用redux和redux-saga的时候,特别是如何存放reducer函数和saga的generator函数,这两个函数是直接跟如何处理数据挂钩的。

  回顾一下,在redux中使用异步中间件redux-saga后,完整的数据和信息流向:

  在存在异步的逻辑下,在UI Component中发出一个plain object的action,然后经过redux-saga这个中间件处理,redux-saga会将这个action传入相应channel,通过redux-saga的effect方法(比如call、put、apply等方法)生成一个描述对象,然后将这个描述对象转化成具有副作用的函数并执行。

  在redux-saga执行具有副作用的函数时,又可以dispatch一个action,这个action也是一个plain object,会直接传入到redux的reducer函数中进行处理,也就是说在redux-saga的task中发出的action,就是同步的action。

简单的概括:从UI组件上发出的action经过了2层的处理,分别是redux-saga中间件和redux的reducer函数。

  redux-dark模式很简单,就是将同一个子页面下的redux-saga处理action的saga函数,以及reducer处理该子页面下的state的reducer函数,放在同一个文件中。

举例来说:

 import { connect } from "react-redux";
 class Hello extends React.Component{
     componentDidMount(){
       //发出一个action,获取异步数据
       this.props.dispatch({
          type:"async_count"
       })
     }
     
 
 }
 export default connect({})(Hello);
 

从Hello组件中发出一个type = "async_count"的action,我们用redux-dark模式来将saga和reducer函数放在同一个文件中:

    import { takeEvery } from "redux-saga/effects";
    
    //saga
    function * asyncCount(){
      console.log("执行了saga异步...")
      //发出一个原始的action
      yield put({
        type:"async"
      });
    }
    function * helloSaga(){
        //接受从UI组件发出的action
        takeEvery("async_count",asyncCount);
    }
    
    //reducer
    function helloReducer(state,action){
       if(action.type === "count");
       return { ...state,count + 1}
    }

上述就是一个将saga和reducer放在同一个文件里面的例子。redux-dark模式来组织代码,可以显得比较直观,统一了数据的处理层。分拆子页面后,每一个子页面对应一个文件。可读性很高。

二、dva 0.0.12版本的使用和源码理解

  上述的redux-dark模式,就是一种简单的处理,而dva的话是做了更近一步的封装,dva不仅封装了redux和redux-saga,还有react-router-redux、react-router等等。使得我们可以通过很简单的配置,就能使用redux、redux-saga、react-router等。

下面首先以dva的初始版本为例来理解一下dva的源码。

(1)、dva 0.0.12的使用

来看官网给的使用dva 0.0.12的例子:

// 1. Initialize
const app = dva();

// 2. Model
app.model({
  namespace: "count",
  state: 0,
  effects: {
    ["count/add"]: function*() {
      console.log("count/add");
      yield call(delay, 1000);
      yield put({
        type: "count/minus",
      });
    },
  },
  reducers: {
    ["count/add"  ](count) { return count + 1 },
    ["count/minus"](count) { return count - 1 },
  },
  subscriptions: [
    function(dispatch) {
      //..处理监听等等函数
    }
  ],
  
});

// 3. View
const App = connect(({ count }) => ({
  count
}))(function(props) {
  return (
    

{ props.count }

); }); // 4. Router app.router(({ history }) => ); // 5. Start app.start(document.getElementById("root"));

只要三步就完成了一个例子,如何处理action呢,我们以一个图来表示:

也就是做UI组件上发出的对象类型的action,先去根据类型匹配=model初始化时候,effects属性中的action type。

如果在effects的属性中有相应的action type的处理函数,那么先执行effects中的相应函数,在执行这个函数里面可以二次发出action,二次发出的action会直接传入到reducer函数中。

如果effects的属性中没有相应的action type的处理函数,那么会直接从reducer中寻找有没有相应类型的处理函数。

在dva初始化过程中的effects属性中的函数,其实就是redux-saga中的saga函数,在该函数中处理直接的异步逻辑,并且该函数可以二次发出同步的action。

此外dva还可以通过router方法初始化路由等。

(2)、dva 0.0.12的源码阅读

下面来直接读读dva 0.0.12的源码,下面的代码是经过我精简后的dva的源码:

//Provider全局注入store
import { Provider } from "react-redux";
//redux相关的api
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
//redux-saga相关的api,takeEvery和takeLatest监听等等
import createSagaMiddleware, { takeEvery, takeLatest } from "redux-saga";
//react-router相关的api
import { hashHistory, Router } from "react-router";
//在react-router4.0之后已经较少使用,将路由的状态存储在store中
import { routerMiddleware, syncHistoryWithStore, routerReducer as routing } from "react-router-redux";
//redux-actions的api,可以以函数式描述reducer等
import { handleActions } from "redux-actions";
//redux-saga非阻塞调用effect
import { fork } from "redux-saga/effects";

function dva() {
  let _routes = null;
  const _models = [];
  //new dva暴露了3个方法
  const app = {
    model,
    router,
    start,
  };
  return app;
  //添加models,一个model对象包含了effects,reducers,subscriptions监听器等等
  function model(model) {
    _models.push(model);
  }
  //添加路由
  function router(routes) {
    _routes = routes;
  }

  
  function start(container) {

    let sagas = {};
    //routing是react-router-redux的routerReducer别名,用于扩展reducer,这样以后扩展后的reducer就可以处理路由变化。
    let reducers = {
      routing
    };
    _models.forEach(model => {
      //对于每一个model,提取其中的reducers和effects,其中reducers用于扩展redux的reducers函数,而effects用于扩展redx-saga的saga处理函数。
      reducers[model.namespace] = handleActions(model.reducers || {}, model.state);
      //扩展saga处理函数,sagas是包含了所有的saga处理函数的对象
      sagas = { ...sagas, ...model.effects }; ---------------------------(1)
    });

    reducers = { ...reducers };
    
    //获取决定使用React-router中的那一个api
    const _history = opts.history || hashHistory;
    //初始化redux-saga
    const sagaMiddleware = createSagaMiddleware();
    //为redux添加中间件,这里添加了处理路由的中间件,以及redux-saga中间件。
    const enhancer = compose(
      applyMiddleware.apply(null, [ routerMiddleware(_history), sagaMiddleware ]),
      window.devToolsExtension ? window.devToolsExtension() : f => f
    );
    const initialState = opts.initialState || {};
    //通过combineReducers来扩展reducers,同时生成扩展后的store实例
    const store = app.store = createStore(
      combineReducers(reducers), initialState, enhancer
    );

    // 执行model中的监听函数,监听函数中传入store.dispatch
    _models.forEach(({ subscriptions }) => {
      if (subscriptions) {
        subscriptions.forEach(sub => {
         store.dispatch, onErrorWrapper);
        });
      }
    });
    
     // 根据rootSaga来启动saga,rootSaga就是redux-saga运行的主task
    sagaMiddleware.run(rootSaga);
    
    
    //创建history实例子,可以监听store中的state的变化。
    let history;
    history = syncHistoryWithStore(_history, store); --------------------------------(2)
    

    // Render and hmr.
    if (container) {
      render();
      apply("onHmr")(render);
    } else {
      const Routes = _routes;
      return () => (
        
          
        
      );
    }

    function getWatcher(k, saga) {
      let _saga = saga;
      let _type = "takeEvery";
      if (Array.isArray(saga)) {
        [ _saga, opts ] = saga;
    
        _type = opts.type;
      }

      function* sagaWithErrorCatch(...arg) {
        try {
          yield _saga(...arg);
        } catch (e) {
          onError(e);
        }
      }

      if (_type === "watcher") {
        return sagaWithErrorCatch;
      } else if (_type === "takeEvery") {
        return function*() {
          yield takeEvery(k, sagaWithErrorCatch);
        };
      } else {
        return function*() {
          yield takeLatest(k, sagaWithErrorCatch);
        };
      }
    }

    function* rootSaga() {
      for (let k in sagas) {
        if (sagas.hasOwnProperty(k)) {
          const watcher = getWatcher(k, sagas[k]);
          yield fork(watcher);
        }                      -----------------------------(3)
      }
    }

    function render(routes) {
      const Routes = routes || _routes;
      ReactDOM.render((
        
          
        
      ), container);
    }
  }
}

export default dva;

代码的阅读在上面都以注视的方式给出,值得注意的主要有一下3点:

在注释(1)处, handleActions是通过redux-actions封装后的一个API,用于简化reducer函数的书写。下面是一个handleActions的例子:

const reducer = handleActions(
  {
    INCREMENT: (state, action) => ({
      counter: state.counter + action.payload
    }),
​
    DECREMENT: (state, action) => ({
      counter: state.counter - action.payload
    })
  },
  { counter: 0 }
);

INCREMENT和DECREMENT属性的函数就可以分别处理,type = "INCREMENT"和type = "DECREMENT"的action。

在注释 (2) 处,通过react-router-redux的api,syncHistoryWithStore可以扩展history,使得history可以监听到store的变化。

在注释(3)处是一个rootSaga, 是redux-saga运行的时候的主Task,在这个Task中我们这样定义:

function* rootSaga() {
  for (let k in sagas) {
    if (sagas.hasOwnProperty(k)) {
      const watcher = getWatcher(k, sagas[k]);
      yield fork(watcher);
    }                     
  }
}

从全局的包含所有saga函数的sagas对象中,获取相应的属性,并fork相应的监听,这里的监听常用的有takeEvery和takeLatest等两个redux-saga的API等。

总结:上面就是dva最早版本的源码,很简洁的使用了redux、redux-saga、react-router、redux-actions、react-router-redux等.其目的也很简单:

简化redux相关生态的繁琐逻辑

参考源码地址:https://github.com/dvajs/dva/...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/117310.html

相关文章

  • React生态dva源码阅读

    摘要:下面会从浅到深,淡淡在阅读源码过程中自己的理解。分拆子页面后,每一个子页面对应一个文件。总结上面就是最早版本的源码,很简洁的使用了等其目的也很简单简化相关生态的繁琐逻辑参考源码地址   dva的思想还是很不错的,大大提升了开发效率,dva集成了Redux以及Redux的中间件Redux-saga,以及React-router等等。得益于Redux的状态管理,以及Redux-saga中...

    txgcwm 评论0 收藏0
  • React生态dva源码阅读

    摘要:下面会从浅到深,淡淡在阅读源码过程中自己的理解。分拆子页面后,每一个子页面对应一个文件。总结上面就是最早版本的源码,很简洁的使用了等其目的也很简单简化相关生态的繁琐逻辑参考源码地址   dva的思想还是很不错的,大大提升了开发效率,dva集成了Redux以及Redux的中间件Redux-saga,以及React-router等等。得益于Redux的状态管理,以及Redux-saga中...

    harryhappy 评论0 收藏0
  • dva系列源码解读

    摘要:介绍概述本次对源码的解读除了传统的从入手外还将引入带入问题读源码的理念,因为只有这样当读完源码之后才会有切身的收获。 介绍 概述 本次对 dva 源码的解读除了传统的从 api 入手外还将引入带入问题读源码的理念,因为只有这样当读完源码之后才会有切身的收获。另外除了 dva 的源码外还会解读一些常用的 dva 插件的源码。 电子书 https://dva-source-docs.net...

    focusj 评论0 收藏0
  • React的移动端和PC端生态圈的使用汇总

    摘要:调用通过注册表调用到实例,透过的,调用到中的,最后通过,调用,根据参数相应模块执行。京东的,多端解决方案是一套遵循语法规范的多端开发解决方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 对于一项技术,我们不能停留在五分钟状态,特别喜欢一句话,用什么方式绘制UI界面一点不重要,重要的是底层的思维,解决问题和优化...

    kun_jian 评论0 收藏0
  • React的移动端和PC端生态圈的使用汇总

    摘要:调用通过注册表调用到实例,透过的,调用到中的,最后通过,调用,根据参数相应模块执行。京东的,多端解决方案是一套遵循语法规范的多端开发解决方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 对于一项技术,我们不能停留在五分钟状态,特别喜欢一句话,用什么方式绘制UI界面一点不重要,重要的是底层的思维,解决问题和优化...

    J4ck_Chan 评论0 收藏0

发表评论

0条评论

bergwhite

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<