笔记参考文档open in new window

上一篇 ▶️ 《深度理解DVA源码》(第一篇)open in new window

src/index.js

为什么不直接在start方式中oldAppStart

  • 因为 dva-core 的 start 方法里有用到 this,不用 call 指定调用者为 app 的话,oldAppStart() 会找错对象。

实现代理模式一定要用到call?

  • 不一定,看有没有 使用 this 或者代理的函数是不是箭头函数。从另一个角度来说,如果使用了 function 关键字又在内部使用了 this,那么一定要用 call/apply/bind 指定 this。

前端还有哪里会用到call?

  • 就实际开发来讲,因为已经使用了 es6 标准,基本和 this 没什么打交道的机会。使用 class 类型的组件中偶尔还会用到 this.xxx.bind(this),stateless 组件就洗洗睡吧(因为压根没有 this)。如果实现代理,可以使用继承/反向继承的方法 —— 比如高阶组件。

使用react-redux的高阶组件传递store

  • 经过call代理后的start方法的主要作用,便是使用react-redux的provider组件将数据与视图联系了起来,生成React元素呈现给使用者。
//使用 querySelector 获得 dom
if (isString(container)) {
  container = document.querySelector(container);
  invariant(
    container,
    `[app.start] container ${container} not found`,
  );
}
// 其他代码

// 实例化 store
oldAppStart.call(app); 
const store = app._store;

// export _getProvider for HMR
// ref: https://github.com/dvajs/dva/issues/469
app._getProvider = getProvider.bind(null, store, app);

// If has container, render; else, return react component
// 如果有真实的 dom 对象就把 react 拍进去
if (container) {
  render(container, store, app, app._router);
  // 热加载在这里
  app._plugin.apply('onHmr')(render.bind(null, container, store, app));
} else {
  // 否则就生成一个 react ,供外界调用
  return getProvider(store, this, this._router);
}
  
 // 使用高阶组件包裹组件
function getProvider(store, app, router) {
  return extraProps => (
    <Provider store={store}>
      { router({ app, history: app._history, ...extraProps }) }
    </Provider>
  );
}

// 真正的 react 在这里
function render(container, store, app, router) {
  const ReactDOM = require('react-dom');  // eslint-disable-line
  ReactDOM.render(React.createElement(getProvider(store, app, router)), container);
}

React.reateElement(getProvider(store,app,router))怎么理解?

  • getProvider实际上返回的不单纯是函数,而是一个无状态的React组件。

怎么理解React的state组件和calss组件

  • 首先,JS并不存在class这个东西,即时是es6引入以后经过babel编译也会转换成函数。
  • 因此直接使用无状态组件,省去了将class实例化再调用render函数的过程,有效的加快了渲染速度。

provider是什么?

  • 其本质是一个高阶组件,也是代理模式的一种实践方式。
  • 接收redux生成的store做参数后,通过上下文context将store传递进被代理组件。
  • 在保留原组件的功能不变的同时,增加了store的dispatch等方法。

connect是什么?

  • connect是一个代理模式实现的高阶组件,为被代理的组件实现了从context中获取store的方法。

connect()(MyComponent)时发生了什么?

import connectAdvanced from '../components/connectAdvanced'
export function ceateConnect ({
  connectHOC = connectAdvanced,
  ... 其他初始值
} = {} ) {
  return function connect ({
    // 0 号 connect
    mapStateToProps,
    mapDispatxhToProps,
    ...其他初始值
  } {} ){
     ...其他逻辑
     return connectHOC(selectorFactory,){
       //1 号 connect
       ... 默认参数
       slectorFactory 默认参数
     })
  }
}

export default createConnect()
//这是connect的本体,导出时即生成connect 0

续...

//hoist-non-react-satics.会自动把所有绑定在对象上的非React方法都绑定到新的对象上
import hoistStatics from 'hoist-non-react-statics'
// 1号 connect 的本体
export default function connectAdvanced(){
  //逻辑处理

  // 1号 connect 调用时生成2号 connect。
  return function wrapWithConnect(WrappedComponent){
    //...逻辑处理

    //在函数内定义一个可以拿到上下文对象中的store的组件
    class Connect extends Component{

      getChildContext(){
        //上下文对象中获得 store
        const subscript = this.propsMode ? null : this.subscription
        return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
      }

      //逻辑处理

      render(){

        // 最终生成了新的react元素,并添加了新的属性
        return createElement(WrappedComponent, this.addExtralProps(selector.props))
      }
    }
    //逻辑处理

    //最后用定义的class和被代理的组件生成新的react组件
    return hoistStatics(Connect. WrappedComponent) // 2 号函数调用后生成的对象是组件
  }
}

结论: 对于connect()(MyComponent)

  • 1.connect调用时生成 0 号 connect
  • 2.connect() 0 号connect调用,返回1号connect的调用connectHOC(),生成 2 2号 connect(也是个函数)
  • 2.connect()(MyComponent)等价于connect2(MyComponent),返回值是一个新的组件。

redux 与 router

redux是状态管理的库,router是(唯一)控制页面跳转的库。两者都很美好,但是让人不爽的是两者无法协同工作。也就是说:当路由变化以后,store无法感知到。 所以,react-router-redux便产生了。

react-router-redux是redux的中间件,主要作用:

  • 加强了React Router库中history这个实例,以允许将history中接受的变化反应到state中去。
    (中间件:JavaScript 代理模式的另一种实践 针对 dispatch 实现了方法的代理,在 dispatch action 的时候增加或者修改)

从代码上讲,主要是监听了 history 的变化:

history.listen(location => analyticsService.track(location.pathname))

dva 在此基础上又进行了一层代理,把代理后的对象当作初始值传递给了 dva-core,方便其在 model 的 subscriptions 中监听 router 变化。

index.js里router的实现

  • 1.在createOpts中初始化了添加的react-router-redux中间件的方法和其reducer,方便dva-core在创建store的时候直接调用。
  • 2.使用patchHistory函数代理history.listen,增加一个回调函数的做参数(也就是订阅)。

subscriptions的东西可以放在dva-core里再说 【?】

import createHashHistory from 'history/createHashHistory';
import {
  routerMiddleware,
  routerReducer as routing,
} from 'react-router-redux';
import * as core from 'dva-core';

export default function (opts = {}) {
  const history = opts.history || createHashHistory();
  const createOpts = {
  	// 	初始化 react-router-redux 的 router
    initialReducer: {
      routing,
    },
	// 初始化 react-router-redux 添加中间件的方法,放在所有中间件最前面
    setupMiddlewares(middlewares) {
      return [
        routerMiddleware(history),
        ...middlewares,
      ];
    },
	// 使用代理模式为 history 对象增加新功能,并赋给 app
    setupApp(app) {
      app._history = patchHistory(history);
    },
  };

  const app = core.create(opts, createOpts);
  const oldAppStart = app.start;
  app.router = router;
  app.start = start;
  return app;

  function router(router) {
    invariant(
      isFunction(router),
      `[app.router] router should be function, but got ${typeof router}`,
    );
    app._router = router;
  }


}

// 使用代理模式扩展 history 对象的 listen 方法,添加了一个回调函数做参数并在路由变化是主动调用
function patchHistory(history) {
  const oldListen = history.listen;
  history.listen = (callback) => {
    callback(history.location);
    return oldListen.call(history, callback);
  };
  return history;
}

redux中创建store的方法为:

// combineReducers 接收的参数是对象
// 所以 initialReducer 的类型是对象
// 作用:将对象中所有的 reducer 组合成一个大的 reducer
const reducers = {}; 
// applyMiddleware 接收的参数是可变参数
// 所以 middleware 是数组
// 作用:将所有中间件组成一个数组,依次执行
const middleware = []; 
const store = createStore(
  combineReducers(reducers),
  initial_state, // 设置 state 的初始值
  applyMiddleware(...middleware)
);

视图与数据

src/index.js主要实现了dva的view层,同时传递了一些初始化数据到dva-core所实现的model层。也提供一些dva中常用的方法函数:

  • dynamic 动态加载
  • fetch 请求方法(dva只是做了一把搬运工)
  • saga (数据层处理异步的方法)

这么看dva真的是很薄的一层封装!

dva-core主要解决了model的问题,包括state管理、数据的异步加载、订阅-发布模式的实现,可以作为数据层在别处使用。使用的状态管理库还是redux,异步加载的解决方案是saga。这一切都写在index.js和package.json中。

处理 React 的 model 层问题有很多种办法,比如状态管理就不一定要用 Redux,也可以使用 Mobx(写法会更有 MVX 框架的感觉);异步数据流也未必使用 redux-saga,redux-thunk 或者 redux-promise 的解决方式也可以(不过目前看来 saga 是相对更优雅的)。

较为全面的技术文档:

接 ▶️ 《深度理解DVA源码》(第三篇)open in new window