上一篇 ▶️ 《深度理解DVA源码》(第一篇)
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源码》(第三篇)