浅析dva (史上最全的dva用法及分析) |
您所在的位置:网站首页 › index的使用方法及实例分析 › 浅析dva (史上最全的dva用法及分析) |
什么是dva
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。学过React的童鞋都知道它的技术栈真的很多,所以每当你使用React的时候都需要引入很多的模块,那么dva就是把这些用到的模块集成在一起,形成一定的架构规范。把react常常需要我们必须写的需要用到的引用、代码都集成在了一起,比如一些依赖、必写的一些ReactDOM.render、引入saga、redux控制台工具、provider包裹等都省去不写,大大提高我们的开发效率增加了一个 Subscriptions, 用于收集其他来源的 action, eg: 键盘操作、滚动条、websocket、路由等 ![]() ![]() 步骤: npm install dva-cli -gdva new dva-quickstart 目录结构![]() 创建应用,返回 dva 实例。(注:dva 支持多实例)。 const app = dva({ history, // 指定给路由用的 history,默认是 hashHistory initialState, // 指定初始数据,优先级高于 model 中的 state onError, // effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态。 onAction, // 在 action 被 dispatch 时触发 onStateChange, // state 改变时触发,可用于同步 state 到 localStorage,服务器端等 onReducer, // 封装 reducer 执行。比如借助 redux-undo 实现 redo/undo onEffect, // 封装 effect onHmr, // 热替换相关 extraReducers, // 指定额外的 reducer,比如 redux-form 需要指定额外的 form reducer extraEnhancers, // 指定额外的 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'此时为1 reducer Action 处理器,处理同步动作,用来算出最新的 State,同redux中的reducer dva对redux做了一层封装,它会把modal里面的 reducers函数, 进行一次key的遍历,每个key为一个reducer,当然它加上命名空间,action type对应的reducer、effect![]() 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',//控制隐藏头部的display footerDisplay: 'none'//控制footer的display } });subscriptions 以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start() 时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。 格式为 ({ dispatch, history }, done) => unlistenFunction。 注意:如果要使用 app.unmodel(),subscription 必须返回 unlisten 方法,用于取消数据订阅。
启动应用。selector 可选,如果没有 selector 参数,会返回一个返回 JSX 元素的函数。 selector为根元素 app.start('#root'); mock—.roadhogrc.mock.jsroadhog 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.js const 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 升级可能会引起兼容问题。 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 在roadhog中引入他们自己封装的af-webpack,这里面用css-loader以及加上.webpackrc的配置对css进行模块化,将css结果js的一层封装,给classname后面加上随机的hash,使得classname不会冲突,若要全局的就加上:global即可 如果当前应用中加载了不止一个model,在其中一个的effect里面做select操作,是可以获取另外一个中的state的: *foo(action, { select }) { const { a, b } = yield select(); } model的动态扩展 注意到dva中的每个model,实际上都是普通的JavaScript对象,可以利用object.assign进行覆盖使用通过工厂函数来生成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' } }); 可以借助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}); else put({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); } 源码浅析 roadhogroadhog主要是依赖于他们自己封装的af-webpack 获取webpackrc的配置以及校验在getUserConfig的文件夹下,直接通过json内容去获取配置
![]() ![]()
![]() ![]() ![]() ![]() ![]() ![]() |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |