什么是dva

  • dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
  • 学过React的童鞋都知道它的技术栈真的很多,所以每当你使用React的时候都需要引入很多的模块,那么dva就是把这些用到的模块集成在一起,形成一定的架构规范。把react常常需要我们必须写的需要用到的引用、代码都集成在了一起,比如一些依赖、必写的一些ReactDOM.render、引入saga、redux控制台工具、provider包裹等都省去不写,大大提高我们的开发效率
  • 增加了一个 Subscriptions, 用于收集其他来源的 action, eg: 键盘操作、滚动条、websocket、路由等
  • 在react-redux上开发的dva+在redux-saga基础上开发的dva-core+在webpack基础上开发的roadhog进行打包启动服务
  • 数据流向(基于redux,所以同react-redux)

    • 输入url渲染对应的组件,该组件通过dispatch去出发action里面的函数,如果是同步的就去进入model的ruducer去修改state,如果是异步比如fetch获取数据就会被effect拦截通过server交互获取数据进而修改state,同样state通过connect将model、状态数据与组件相连

简单快速的dva项目

步骤:

  1. npm install dva-cli -g
  2. dva new dva-quickstart
  • 目录结构

src

index.js(入口文件)

app = dva(opts)

创建应用,返回 dva 实例。(注:dva 支持多实例)。

const app = dva({history, // 指定给路由用的 history,默认是 hashHistoryinitialState,  // 指定初始数据,优先级高于 model 中的 stateonError, // effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态。onAction, // 在 action 被 dispatch 时触发onStateChange, // state 改变时触发,可用于同步 state 到 localStorage,服务器端等onReducer, // 封装 reducer 执行。比如借助 redux-undo 实现 redo/undoonEffect, // 封装 effectonHmr, // 热替换相关extraReducers, // 指定额外的 reducer,比如 redux-form 需要指定额外的 form reducerextraEnhancers, // 指定额外的 StoreEnhancer ,比如结合 redux-persist 的使用
});

这里可以对以下的hook进行option配置
这里可以将hashhistory转化为browserHistory

import createHistory from 'history/createBrowserHistory';
const app = dva({history: createHistory(),
});
app.use(hooks)

同样可以配置hooks以及注册其他插件

import createLoading from 'dva-loading';
...
app.use(createLoading(opts));
app.model

在普通的react-redux+redux-saga的项目中,我们首先会建4个文件夹,分别是actions,reducer,saga,组件,还有获取请求数据的services文件夹,同样在入口文件那要引入很多中间件、provider、connect等去将这几个文件夹联系起来,在这里的model以下就将这些集成在了一起,大大减小了开发工作量。

  • namespace
    model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 . 的方式创建多层命名空间。相当于这个model的key
    在组件里面,通过connect+这个key将想要引入的model加入

      import { connect } from 'dva'
    

  • state
    为状态值的初始值,优先级要低于app.dva({})
const app = dva({initialState: { count: 1 },
});
app.model({namespace: 'count',state: 0,
});

此时为1

  • reducer
    Action 处理器,处理同步动作,用来算出最新的 State,同redux中的reducer
    dva对redux做了一层封装,它会把modal里面的 reducers函数, 进行一次key的遍历,每个key为一个reducer,当然它加上命名空间,action type对应的reducer、effect
  • effect
    Action 处理器,处理异步动作,基于 Redux-saga 实现。Effect 指的是副作用。根据函数式编程,计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写。以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state。由 action 触发,可以触发 action,可以和服务器交互,可以获取全局 state 的数据等等
    通过generate yield以及saga里面的常用call、put、takeEvery、takeLatest、take
  • call 进行触发异步操作
  • put 相当于dispatch 触发reducer改变state
['setQuery']: [function*() {}, { type: 'takeEvery'}],
- takeEvery监听action的每次变化执行(默认)
- takeLatest监听action最近一次的变化
- take监听一次action留着,后面执行动作
  • 为什么要把同步和异步的分开呢
    需要注意的是 Reducer 必须是纯函数,所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且,每一次的计算都应该使用immutable data,这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。

Effect 被称为副作用,在我们的应用中,最常见的就是异步操作。它来自于函数编程的概念,之所以叫副作用是因为它使得我们的函数变得不纯,同样的输入不一定获得同样的输出。

dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。至于为什么我们这么纠结于 纯函数,如果你想了解更多可以阅读Mostly adequate guide to FP,或者它的中文译本JS函数式编程指南。

纯函数的好处:将函数抽离出来,与业务不耦合

 更有利于单元测试无副作用(side-effect),不会修改作用域外的值,使代码好调试执行顺序不会对系统造成影响剥离出业务逻辑,好复用
  • action跑哪去了?
    action在组件的dispath中触发,dva对redux做了一层封装,它会把modal里面的 reducers函数, 进行一次key的遍历,每个key为一个reducer,当然它加上命名空间,action type对应的reducer、effect
const { dispatch } = this.props;dispatch({ type: 'app/updateState' ,payload: {opacityTop: 'none',//控制top的透明度hiddenDivDisplay: 'none',//控制隐藏头部的displayfooterDisplay: 'none'//控制footer的display}});
  • subscriptions
    以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start() 时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。

      格式为 ({ dispatch, history }, done) => unlistenFunction。注意:如果要使用 app.unmodel(),subscription 必须返回 unlisten 方法,用于取消数据订阅。
    


app.router


直接将路由引入或在多页应用中只将组件引入

app.router(require('./router'));
app.router(() => <App />);
app.start

启动应用。selector 可选,如果没有 selector 参数,会返回一个返回 JSX 元素的函数。
selector为根元素

app.start('#root');

mock—.roadhogrc.mock.js

roadhog server 支持 mock 功能,类似 dora-plugin-proxy,在 .roadhogrc.mock.js 中进行配置,支持基于 require 动态分析的实时刷新,支持 ES6 语法,以及友好的出错提示。在配置文件进行一下(node语法)配置,就可以通过简单的fetch请求获取到数据。

.roadhogrc.mock.js
export default {// 支持值为 Object 和 Array'GET /api/users': { users: [1,2] },// GET POST 可省略'/api/users/1': { id: 1 },// 支持自定义函数,API 参考 express@4'POST /api/users/create': (req, res) => { res.end('OK'); },// Forward 到另一个服务器'GET /assets/*': 'https://assets.online/',// Forward 到另一个服务器,并指定子路径// 请求 /someDir/0.0.50/index.css 会被代理到 https://g.alicdn.com/tb-page/taobao-home, 实际返回 https://g.alicdn.com/tb-page/taobao-home/0.0.50/index.css'GET /someDir/(.*)': 'https://g.alicdn.com/tb-page/taobao-home',
};

若为多接口应用,则在mock文件夹下利用mockjs进行数据模拟,再在配置文件里,进行文件遍历引入

mock->user.js
const qs = require('qs');
const mockjs = require('mockjs');  //导入mock.js的模块const Random = mockjs.Random;  //导入mock.js的随机数// 数据持久化   保存在global的全局变量中
let tableListData = {};if (!global.tableListData) {const data = mockjs.mock({'data|100': [{'id|+1': 1,'name': () => {return Random.cname();},'mobile': /1(3[0-9]|4[57]|5[0-35-9]|7[01678]|8[0-9])\d{8}/,}],page: {total: 100,current: 1,},});tableListData = data;global.tableListData = tableListData;
} else {tableListData = global.tableListData;
}module.exports = {//post请求  /api/users/ 是拦截的地址   方法内部接受 request response对象'GET /users' (req, res) {setTimeout(() => {res.json({      //将请求json格式返回success: true,data,page: '123',});}, 200);},.roadhogrc.mock.jsconst mock = {}
require('fs').readdirSync(require('path').join(__dirname + '/mock')).forEach(function(file) {Object.assign(mock, require('./mock/' + file))
})
module.exports = mock

.webpackrc

格式为 JSON,允许注释,布尔类型的配置项默认值均为 false,支持通过 webpack.config.js 以编码的方式进行配置,但不推荐,因为 roadhog 本身的 major 或 minor 升级可能会引起兼容问题。

  • entry:设置入口文件
  • disableCSSModules:设置是否css模块化
  • publicPath:
  • outputPublic:
  • extraBabelPlugins
    配置额外的 babel plugin。babel plugin 只能添加,不允许覆盖和删除。比如,同时使用 antd, dva 时,通常需要这么配:
    "extraBabelPlugins": ["transform-runtime","dva-hmr",["import", { "libraryName": "antd", "libraryDirectory": "lib", "style": "css" }]]
  • proxy
    配置代理,详见 webpack-dev-server#proxy。如果要代理请求到其他服务器,可以这样配:
"proxy": {"/api": {"target": "http://jsonplaceholder.typicode.com/","changeOrigin": true,"pathRewrite": { "^/api" : "" }}
}
  • multipage
    配置是否多页应用。多页应用会自动提取公共部分为 common.js 和 common.css 。

  • define
    配置 webpack 的 DefinePlugin 插件,define 的值会自动做 JSON.stringify 处理。

  • env
    针对特定的环境进行配置。server 的环境变量是 development,build 的环境变量是 production。防止生产环境冗余。

"extraBabelPlugins": ["transform-runtime"],
"env": {"development": {"extraBabelPlugins": ["dva-hmr"]}
}
  • theme
    配置主题,实际上是配 less 的 modifyVars。支持 Object 和文件路径两种方式的配置。结合antd设置全局样式。
"theme": {"@primary-color": "#1DA57A"
}
/
"theme": "./node_modules/abc/theme-config.js"

段位升级

dva/dynamic(懒加载)

在router.js中使用,动态加载model和component
app: dva 实例,加载 models 时需要
models: 返回 Promise 数组的函数,Promise 返回 dva model
component:返回 Promise 的函数,Promise 返回 React Component

css 模块化

在roadhog中引入他们自己封装的af-webpack,这里面用css-loader以及加上.webpackrc的配置对css进行模块化,将css结果js的一层封装,给classname后面加上随机的hash,使得classname不会冲突,若要全局的就加上:global即可

用model共享全局信息

如果当前应用中加载了不止一个model,在其中一个的effect里面做select操作,是可以获取另外一个中的state的:

*foo(action, { select }) {const { a, b } = yield select();
}

model的动态扩展

  1. 注意到dva中的每个model,实际上都是普通的JavaScript对象,可以利用object.assign进行覆盖使用
  2. 通过工厂函数来生成model
function createModel(options) {const { namespace, param } = options;return {namespace: `demo${namespace}`,states: {},reducers: {},effects: {*foo() {// 这里可以根据param来确定下面这个call的参数yield call()}}};
}const modelA = createModel({ namespace: 'A', param: { type: 'A' } });
const modelB = createModel({ namespace: 'A', param: { type: 'B' } });
  1. 可以借助dva社区的dva-model-extend库来做这件事

多任务调度

  • 任务的并行执行
const [result1, result2]  = yield all([call(service1, param1),call(service2, param2)
])
  • 任务的竞争
const { data, timeout } = yield race({data: call(service, 'some data'),timeout: call(delay, 1000)
});if (data)put({type: 'DATA_RECEIVED', data});
elseput({type: 'TIMEOUT_ERROR'});

跨model的通信

如果这里是要在组件里面做某些事情,怎么办?
将resolve传给model

new Promise((resolve, reject) => {dispatch({ type: 'reusable/addLog', payload: { data: 9527, resolve, reject } });
})
.then((data) => {console.log(`after a long time, ${data} returns`);
});

在model进行跨model通信

try {const result = yield call(service1);yield put({ type: 'service1Success', payload: result });resolve(result);
}
catch (error) {yield put({ type: 'service1Fail', error });reject(ex);
}

源码浅析

roadhog

roadhog主要是依赖于他们自己封装的af-webpack

获取webpackrc的配置以及校验

在getUserConfig的文件夹下,直接通过json内容去获取配置

在config下进行每一项的校验

在index,js中调用watch.js的方法去监听我们的配置文件,而监听文件夹用的是chokidar这个包

css模块化



通过我们的配置文件的配置,以及对环境对判断,动态给classname的后面加上hash值

dva


通过对其package.json的研究可以看出,这个下面只是对fetch、redux、router进行封装

dva/dynamic

  • 首先通过传入的model以及component利用promise.all进行加载,先判断是否有model,没有model就直接将component传出去,有model的话,就在此动态加载model(registerModel)

    这里利用app.model进行注册,当然我们也可以利用这个方法去扩展卸载的方法app.unmodel
  • 那组件传到哪了呢?

    在这里看到其实我们的dynamic里面还可以传一个参数就是默认加载的组件,为LoadingComponent,利用该默认组件的生命周期的加载,去控制我们传入的component的设置,赋值到AsyncComponent,
  • 那为什么有this.state.AsyncComponent = AsyncComponent;这种写法
    防止渲染速度太快,导致默认组件还没有挂在上,直接渲染async组件
  • 当async为null的时候,就只渲染默认组件,从这可以看出component为必填选项
index.js


从这就可以看出在这里将provider、render等在此编写

而这些都是在app.start中完成的,并且router必须在start前注册

同样,在dva({})初始化的时候,将中间件注册并返回了一个app的对象

利用react-router-redux的routerReducer进行action的路由跳转,并将routing可以返回给组件使用

dva-core


通过package.json就知道这里是对redux-saga进行封装

model

  • 首先在checkModel中进行5个api的校验
  • 在prefixNamespace中对每个model加上key的前缀,以便可以将其当作action的type去dispatch
  • 在createStore中将中间件以及saga进行注册
  • 在getSaga getReducer中将reducer、effect功能实现
  • 在这里也是返回了一个app的对象实现start具体功能以及use

    c从卸载的函数里面就可以看到他是把model也都存在一个store里面,通过dispatch去触发删除model
  • subscription
    Object.prototype.hasOwnProperty.call(subs, key) 还是使用原型方法判断 key 是不是 subs 的自有属性
    如果是自由属性,那么拿到属性对应的值(是一个 function)
    调用该 function,传入 dispatch 和 history 属性。history 就是经过 redux-router 强化过的 history,而 dispatch,也就是 prefixedDispatch(app._store.dispatch, model)
  • prefixedDispatch: 就是给dispatch方法加上namespace的前缀

浅析dva (史上最全的dva用法及分析)相关推荐

  1. 史上最全的JFinal源码分析(不间断更新)

    打算 开始 写 这么 一个系列,希望 大家 喜欢,学习 本来就是 一个查漏补缺的过程,希望大家能提出建议.本篇 文章 是整个目录的向导,希望 大家 喜欢.本文 将以 包的形式跟大家做向导. Handl ...

  2. 史上最全的sqlserver运维分析工具,汇总都在这里了,适合sqlserver的dba人员

    比较常用的sqlserver运维分析语句 SELECT TOP 2000 ST.text AS '执行的SQL语句',QS.execution_count AS '执行次数',QS.total_ela ...

  3. 操作系统面试题(史上最全、持续更新)

    尼恩面试宝典专题40:操作系统面试题(史上最全.持续更新) 本文版本说明:V28 <尼恩面试宝典>升级规划为: 后续基本上,每一个月,都会发布一次,最新版本,可以联系构师尼恩获取, 发送 ...

  4. 移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

    1.前言 本文接上篇<移动端IM开发者必读(一):通俗易懂,理解移动网络的"弱"和"慢">,关于移动网络的主要特性,在上篇中已进行过详细地阐述,本文 ...

  5. 这可能是史上最全的Python算法集!

    来源 | CSDN(ID:CSDNnews ) 本文是一些机器人算法(特别是自动导航算法)的Python代码合集. 其主要特点有以下三点:选择了在实践中广泛应用的算法:依赖最少:容易阅读,容易理解每个 ...

  6. java spring框架 注解_史上最全的java spring注解

    史上最全的java spring注解,没有之一 注解是个好东西,但好东西我们也是看见过,整理过,理解过,用过才知道好.不求我们每个都记住,但求保有印象,在需要的时候能提取出来再查找相关资料,平时工作就 ...

  7. 史上最全 Java 多线程面试题及答案

    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题. 这些多线程的问题,有些来源于各大网站.有些来源于自己的思考.可能有些问题网上有.可能有些问题对应的答案也有.也可能有些各位网友也 ...

  8. .Net魔法堂:史上最全的ActiveX开发教程——发布篇

    一. 前言 接着上一篇<.Net魔法堂:史上最全的ActiveX开发教程--开发篇>,本篇讲述如何发布我们的ActiveX. 二.废话少讲,马上看步骤! 1. 打包  C#开发的Activ ...

  9. 史上最全《知识图谱》2020综述论文!!!

    关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! 知识图谱是当下的研究热点.最近18位学者共同撰写了一篇<知识图谱>综述 ...

最新文章

  1. ess用户名和密码_陈ess洁如何从摄影系学生转变为成功的自由职业者和内容创作者(播客)...
  2. SQL SERVER 2014 下IF EXITS 居然引起执行计划变更的案例分享
  3. API Gateway
  4. 邮件服务器 文件服务器,搭建邮件、终端和文件服务器应用方案_服务器_服务器x86服务器-中关村在线...
  5. Python 任意中文文本生成词云 最终版本
  6. 网页中图片大小类型等属性不可用
  7. centos上使用高版本gcc、g++
  8. C++基础知识(三)—— 常量
  9. matlab动画_弹簧振子振动的matlab动画演示
  10. 谈论源码_为什么每个人都在谈论WebAssembly
  11. ARM公司的Cortex-M0概况介绍
  12. 数据恢复软件FinalData
  13. 物联网安全行业调研报告 - 市场现状分析与发展前景预测
  14. 使用matlab编写协方差矩阵计算矩阵
  15. CAD对话框不见后要如何调出
  16. 四网协同之WLAN专利分析与启示
  17. 2018.9.13 贷款月供计算器
  18. TensorRT学习(1):通过pth生成wts文件
  19. 软考系统集成项目管理工程师模拟题
  20. FaceBoxes: A CPU Real-time Face Detector with High Accuracy

热门文章

  1. 微信h5授权获取用户openId的方法和步骤,用于用户登录和注册
  2. ASP.Net网页从打版到发布
  3. 职业程序员必看之文章
  4. vue根据按钮进行中英文切换
  5. a 便签实现 下载
  6. php日常工作便签源码,php便签
  7. 复旦大学计算机学院 预推免,学霸的开挂保研经历:专业第一,六级595,三作SCI,斩获同济、南大、西交直博OFFER,最终圆梦复旦!——以梦为马,不负韶华...
  8. Google源码国内镜像源
  9. java-php-python-ssm药品自动贩卖系统计算机毕业设计
  10. vue 表格 侧边(竖向)表头展示数据