前言

  • 第一篇是基本应用与初步实现,第二篇是dva-loading实现,顺便实现了2个钩子。
  • 这篇讲dynamic。

dynamic

  • dynamic可以解决组件动态加载问题
  • 先看使用:
import dynamic from 'dva/dynamic'
const DynamicPage = dynamic({app,models: () => [import('./models/mymodel')],component: () => import('./routes/mypage')
})
app.router(({ history, app }) => (<Router history={history}><><ul><li><Link to='/dynamic'>dynamic</Link></li><li><Link to='/counter1'>counter1</Link></li><li><Link to='/counter2'>counter2</Link></li></ul><Route path='/counter1' component={ConnectedCounter1}></Route><Route path='/counter2' component={ConnectedCounter2}></Route><Route path='/dynamic' component={DynamicPage}></Route></></Router>
)
app.start('#root')
  • 我们需要在dynamic里导入model的配置项以及组件:

models/mymodel

const delay = ms => new Promise(function (resolve) {setTimeout(() => {resolve()}, ms)
})
export default {namespace: 'mymodel',state: { number1: 0, number2: 0 },reducers: {add(state, action) {return { number1: state.number1 + 1, number2: state.number2 + action.payload }}},effects: {*asyncAdd(action, { put, call }) {yield call(delay, 1000)yield put({ type: 'add', payload: 5 })}}
}

routes/mypages

import React from 'react'
import { connect } from 'dva'function Mypage(props) {return (<div><div>{props.number1},{props.number2}</div><button onClick={() => props.dispatch({ type: 'mymodel/asyncAdd' })}>派发</button></div>)
}
export default connect(state => state.mymodel)(Mypage)
  • 简单写个组件,就是异步执行saga会派发add。
  • 这个组件在切换时候就是动态加载的了。
  • 可以打开network 点击link进行切换,发现webpack帮我们做好了懒加载。点跳转的时候出来2个chunk.js。
  • 特别注意这里的connect,使用自己写的dva一定要把它调成自己的connect或者2.60版以上的dva,否则会报“Could not find “store” in either the context or props of …”错误,这个bug让我找了半天,我当时是2.41的dva,后来把版本换来换去才发现原来是这个地方的问题。

dynamic实现

  • 这东西实现很巧妙,第一次看绝对大受启发。
  • 我们在调用dynamic时候,只是去把model和component传给他,同时也是借用了webpack的懒加载。
  • 数据问题先放一边, 先将其渲染出来。
import React from 'react'
const DefaultLoadingComponent = props => <div>加载中</div>
export default function dynamic(config) {let { app, models, component } = configreturn class extends React.Component {constructor(props) {super(props)this.LoadingComponent = config.LoadingComponent || DefaultLoadingComponentthis.state = { AsyncComponent: null }this.load()}async load() {let [resolvedmodule, AsyncComponent] = await Promise.all([Promise.all(models()), component()])resolvedmodule = resolvedmodule.map((m) => m['default'] || m)AsyncComponent = AsyncComponent['default'] || AsyncComponentresolvedmodule.forEach((m) => app.model(m))this.setState({ AsyncComponent })}render() {let { AsyncComponent } = this.statelet { LoadingComponent } = thisreturn (AsyncComponent ? <AsyncComponent {...this.props}></AsyncComponent> : <LoadingComponent></LoadingComponent>)}}
}
  • 这个其实就是个高阶组件,拿到配置项后去执行配置项的Model和component,然后拿到的model要拿去app里把namespace之类的注册上,虽然现在暂时还不能注入。最后选择性渲染component。
  • model和component未在app上注册完时,AsyncComponent是拿不到值的,所以会渲染Loading组件,promise.all完成后则会拿到组件来渲染它。
  • 下面需要实现model注册,这个难点就在于动态加载时,dva实例已经运行了,而要注入新的model,需要改写reducer和saga,同时还需要保存已经加载的reducer和saga。
  • 首先需要先把getReducer函数进行拆分:
    let reducers = {router: connectRouter(app._history)}//改为initialreducers并提到外面for (let m of app._models) {//m是每个model的配置initialReducers[m.namespace] = getReducer(m)}function getReducer(m) {return function (state = m.state, action) {//组织每个模块的reducerlet everyreducers = m.reducers//reducers的配置对象,里面是函数let reducer = everyreducers[action.type]//相当于以前写的switchif (reducer) {return reducer(state, action)}return state}
}// 提到start执行时再进行装载function createReducer() {let extraReducers = plugin.get('extraReducers')return combineReducers({...initialReducers,...extraReducers//这里是传来的中间件对象})//reducer结构{reducer1:fn,reducer2:fn}}//最后的合并单独提出来做个函数。
  • 因为我们需要装载时得到一遍,懒加载再拿到一遍。到时候传来装载的model,然后getReducer就可以得到这个model的reducer了。
  • 同理,saga和subscription也要拆一下。使得我们传入model可以直接得到effects和subscriptions。
let sagas = getSagas(app)
for (let m of app._models) {if (m.subscriptions) {runSubscription(m)}}function runSubscription(m) {for (let key in m.subscriptions) {let subscription = m.subscriptions[key]subscription({ history, dispatch: app._store.dispatch })}}function getSagas(app) {let sagas = []for (let m of app._models) {sagas.push(getSaga(m, plugin.get('onEffect')))}return sagas}
  • 下面需要改写model方法,在一开始装载的时候,model方法是加前缀,然后把model存到闭包里,最后start时候才进行装载。但是我们插件再去调model的话只能存进闭包,没人去调它,所以需要改写model。有人说用其他方法不也可以实现吗?确实可以实现,不过需要其他插件在获取model后调用使用别的方法,而不是去执行app.model。既然约定用model方法那就使用这个方法。
       function model(m) {let prefixmodel = prefixResolve(m)app._models.push(prefixmodel)return prefixmodel}app.model = injectModel.bind(app)//都执行完把model方法改了,以后会走injectfunction injectModel(m) {m = model(m)//加前缀initialReducers[m.namespace] = getReducer(m)//此时的initialReducers是一开始装载后的,只要再添加新的替换调即可。store.replaceReducer(createReducer())if (m.effects) {sagaMiddleware.run(getSaga(m, plugin.get('onEffect')))}if (m.subscriptions) {runSubscription(m)}}
  • 首先这个app.model=injectModel.bind(app)表示装载后调app.model实际执行的时injectModel这个方法。
  • 这个model方法为了给后面懒加载使用,需要有个返回值,把带前缀的返回回来,确保reducer和effects能带上前缀。
  • 由于此时拿到的initialReducers就是已经装载过的,也就是包括前面一开始加载时用户配置的model,这时只要往上面添加键就是在原有基础上添加reducer了。
  • 最后使用store提供的replace方法进行替换。
  • 而saga需要中间件去run一下,subscription直接调方法就行了。
  • 另外需要改chunk名字就靠魔法字符串就行了,这是webpack的内容。/* webpackChunkName: "xxxxx" */
  • 目前整个手写dva index.js代码如下,下篇继续。
import React from 'react'
import ReactDOM from 'react-dom'
import { createHashHistory } from 'history'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider, connect } from 'react-redux'
import createSagaMiddleware from 'redux-saga'
import * as sagaEffects from 'redux-saga/effects'
import { routerMiddleware, connectRouter, ConnectedRouter } from 'connected-react-router'
import Plugin, { filterHooks } from './plugin'export default function (opts = {}) {let history = opts.history || createHashHistory()let app = {_models: [],model,router,_router: null,start,_history: history}function model(m) {let prefixmodel = prefixResolve(m)app._models.push(prefixmodel)return prefixmodel}function router(router) {app._router = router}let plugin = new Plugin()plugin.use(filterHooks(opts))app.use = plugin.use.bind(plugin)function getSaga(m, onEffect) {return function* () {for (const key in m.effects) {//key就是每个函数名const watcher = getWatcher(key, m.effects[key], m, onEffect)yield sagaEffects.fork(watcher) //用fork不会阻塞}}}function getSagas(app) {let sagas = []for (let m of app._models) {sagas.push(getSaga(m, plugin.get('onEffect')))}return sagas}let initialReducers = {router: connectRouter(app._history)}function createReducer() {let extraReducers = plugin.get('extraReducers')return combineReducers({...initialReducers,...extraReducers//这里是传来的中间件对象})//reducer结构{reducer1:fn,reducer2:fn}}function getReducer(m) {return function (state = m.state, action) {//组织每个模块的reducerlet everyreducers = m.reducers//reducers的配置对象,里面是函数let reducer = everyreducers[action.type]//相当于以前写的switchif (reducer) {return reducer(state, action)}return state}}function runSubscription(m) {for (let key in m.subscriptions) {let subscription = m.subscriptions[key]subscription({ history, dispatch: app._store.dispatch })}}function start(container) {for (let m of app._models) {//m是每个model的配置initialReducers[m.namespace] = getReducer(m)}let reducer = createReducer()let sagas = getSagas(app)//let store = createStore(reducer)let sagaMiddleware = createSagaMiddleware()let store = applyMiddleware(routerMiddleware(history), sagaMiddleware)(createStore)(reducer)app._store = storefor (let m of app._models) {if (m.subscriptions) {runSubscription(m)}}window.store = app._store//调试用sagas.forEach(sagaMiddleware.run)ReactDOM.render(<Provider store={app._store}><ConnectedRouter history={history}>{app._router({ app, history })}</ConnectedRouter></Provider>, document.querySelector(container))app.model = injectModel.bind(app)//都执行完把model方法改了,以后会走injectfunction injectModel(m) {m = model(m)//加前缀initialReducers[m.namespace] = getReducer(m)//此时的initialReducers是一开始装载后的,只要再添加新的替换调即可。store.replaceReducer(createReducer())if (m.effects) {sagaMiddleware.run(getSaga(m, plugin.get('onEffect')))}if (m.subscriptions) {runSubscription(m)}}}return app
}function prefix(obj, namespace) {return Object.keys(obj).reduce((prev, next) => {//prev收集,next是每个函数名let newkey = namespace + '/' + nextprev[newkey] = obj[next]return prev}, {})
}
function prefixResolve(model) {if (model.reducers) {model.reducers = prefix(model.reducers, model.namespace)}if (model.effects) {model.effects = prefix(model.effects, model.namespace)}return model
}function prefixType(type, model) {if (type.indexOf('/') == -1) {//这个判断有点不严谨,可以自己再捣鼓下return model.namespace + '/' + type}return type//如果有前缀就不加,因为可能派发给别的model下的
}function getWatcher(key, effect, model, onEffect) {function put(action) {return sagaEffects.put({ ...action, type: prefixType(action.type, model) })}return function* () {yield sagaEffects.takeEvery(key, function* (action) {//对action进行监控,调用下面这个sagaif (onEffect) {for (const fn of onEffect) {//oneffect是数组effect = fn(effect, { ...sagaEffects, put }, model, key)}}yield effect(action, { ...sagaEffects, put })})}
}
export { connect }

【dva】dva使用与实现(三)相关推荐

  1. dva源码解析(三)

    转载   原文 https://blog.csdn.net/zhangrui_web/article/details/79651448 API 输出文件 dva 默认输出文件. dva/router ...

  2. umi+dva dva全局的dispatch方法

    情况:有三个组件用到了同一个方法,后期需要维护,所以抽离出来写在了组件外部,当页面有操作时,需要在外部dispatch一些数据,由于不是在hooks文件中,所以不能使用useDispatch. 之前公 ...

  3. react+dva+antd接口调用方式

    一丶 安装 通过 npm 安装 dva-cli 并确保版本是0.8.1或以上. $ npm install dva-cli -g $ dva -v 0.8.1 二丶创建新应用 安装完dva-cli之后 ...

  4. antd 进行ajax请求,react+dva+antd接口调用方式

    一丶 安装 通过 npm 安装 dva-cli 并确保版本是0.8.1或以上. $ npm install dva-cli -g $ dva -v 0.8.1 二丶创建新应用 安装完dva-cli之后 ...

  5. antd 怎么用ajax,react+dva+antd接口调用方式

    一丶 安装 通过 npm 安装 dva-cli 并确保版本是0.8.1或以上.$ npm install dva-cli -g $ dva -v0.8.1 二丶创建新应用 安装完dva-cli之后,就 ...

  6. dva的简单使用(一)

    一.前言 在实际的前端开发中,像 cardList 中包含的那些数据,一般都是通过发起异步 http 请求从后端服务中获得. 我们希望把数据逻辑(cardList 相关逻辑)和视图逻辑(PuzzleC ...

  7. Dva.js 快速上手指南

    先说些废话 最近在开发React技术栈的项目产品,对于数据状态的管理使用了Dva.js,作为一个资深的ow玩家,我看到这个名字第一反应就是----这不是ow里的一个女英雄吗?仔细阅读了官方文档之后,发 ...

  8. React+DVA开发实践

    原文链接 文档概述 本文档在前面章节简单的介绍了React和其相关的一系列技术,最后章节介绍了React+Dva开发的整套过程和基本原理,也就是将一系列框架整合的结果. 文档结构 本文档划分为以下章节 ...

  9. 学习react前端框架dva

    dva 是由阿里架构师 sorrycc 带领 team 完成的一套前端框架,在作者的 github 里是这么描述它的:"dva 是 react 和 redux 的最佳实践". 一. ...

最新文章

  1. Oracle报错createPool,Jmeter中连接Oracle报错Cannot create PoolableConnectionFactory
  2. 【文文殿下】数论一些经典结论
  3. 数据结构与算法 —— 二叉树
  4. MATLAB实战系列(四)- LabVIEW初探
  5. 恶意软件伪装“正规军”,撕开Booster Cleaner“画皮”下的真相
  6. 审计署计算机培训心得体会,审计署计算机中级培训心得体会2018
  7. 【POJ - 2909 】Goldbach's Conjecture (哥德巴赫猜想,数论,知识点结论)
  8. 请求头Content-Type:application/json,java后端如何接收数据
  9. C语言字母的压缩,C语言字符串快速压缩算法代码
  10. 令人笑喷的56个代码注释,最后几个老衲实在憋不住了。。。
  11. python利用tensorflow识别图形_表情识别与性别识别 实时识别模型附源代码 基于python的tensorflow与keras...
  12. 台达plc编程支持c语言吗,台达plc编程软件有哪些
  13. 达梦数据库html管理,达梦数据库的管理 - osc_nbqoh20k的个人空间 - OSCHINA - 中文开源技术交流社区...
  14. Python数据可视化-基于Python-matplotlib
  15. java授权失败_鉴权失败 =(-200)both online
  16. 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三_zzjlzx-ChinaUnix博客...
  17. 无线路由器怎么连接移动wifi来使用
  18. 毕业设计 Stm32人体心率血氧无线监测系统 - 单片机 物联网
  19. OpenXml操作Word的一些操作总结. - 天天不在
  20. Air202入坑指南4---UART2(简单使用)

热门文章

  1. 作为传送信息的公共通道 微型计算机的系统,微型计算机的系统总线是CPU与其他部件之间传送( )信息的公共通道...
  2. 银行信用卡办卡申请进度查询API接口地址
  3. [MATLAB] 读取ASII文件中的复数数据
  4. Linux/Keychron键盘 功能键F1-F12映射修复
  5. 洛谷 #2197. Nim游戏
  6. 报告!优维科技EasyOps®️全栈运维平台又一大波新功能上线
  7. 前端入门学习笔记(三十五)vue.js入门(三)条件 v-if 与循环 v-for,v-for 中 in 和 of 的区别
  8. 最短路径(迪杰斯特拉、弗洛伊德含代码)
  9. Matlab滤波器设计示例
  10. linux 可视化分区,可视化linux块设备的工具(分区,LVM PV,LV,mdadm设备……)