内容简介:创建应用,返回 dva 实例。(注:dva 支持多实例)。这里可以对以下的hook进行option配置 这里可以将hashhistory转化为browserHistory同样可以配置hooks以及注册其他插件
- 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项目
步骤:
- npm install dva-cli -g
- dva new dva-quickstart
- 目录结构
src
index.js(入口文件)
app = dva(opts)
创建应用,返回 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' 复制代码
- 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',//控制隐藏头部的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 方法,用于取消数据订阅。 复制代码
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.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 升级可能会引起兼容问题。
- 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的动态扩展
- 注意到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); } 复制代码
源码浅析
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为必填选项
通过package.json就知道这里是对redux-saga进行封装
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
ActionScript 3.0 Cookbook
Joey Lott、Darron Schall、Keith Peters / Adobe Dev Library / 2006-10-11 / GBP 28.50
Well before Ajax and Microsoft's Windows Presentation Foundation hit the scene, Macromedia offered the first method for building web pages with the responsiveness and functionality of desktop programs......一起来看看 《ActionScript 3.0 Cookbook》 这本书的介绍吧!