初学DvaJS,掌握核心概念后原来如此简单
钉钉、微博极速扩容黑科技,点击观看阿里云弹性计算年度发布会!>>>
dva是什么
dva 首先是一个基于 redux和 redux-saga的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router和 fetch,所以也可以理解为一个轻量级的应用框架。
dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装。dva 是 react 和 redux 的最佳实践。最核心的是提供了 app.model 方法,用于把 reducer, initialState, action, saga 封装到一起。
特性
- 易学易用,仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用更是降低为 0 API
- elm 概念,通过 reducers, effects 和 subscriptions 组织 model
- 插件机制,比如 dva-loading可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
- 支持 HMR,基于 babel-plugin-dva-hmr实现 components、routes 和 models 的 HMR
安装
创建一个dva项目很简单,dva为我们提供了dva-cli,用来快速创建dva项目。
通过 npm 安装 dva-cli 并确保版本是 0.9.1 或以上。
$ npm install dva-cli -g $ dva -v dva-cli version 0.10.1 dva version 2.4.1 roadhog version 2.5.0-beta.4
创建应用
$ dva new dva-quickstart
运行
$ cd dva-quickstart $ npm start Compiled successfully! You can now view Your App in the browser. Local: http://localhost:8000/ On Your Network: http://192.168.43.29:8000/ Note that the development build is not optimized. To create a production build, use npm run build.
运行成功后,会自动打开浏览器,默认为4000端口,可以看到下面的界面
目录结构
│ .editorconfig # 编辑器配置 │ .eslintrc # eslint配置 │ .gitignore # 配置git忽略目录或文件 │ .roadhogrc.mock.js# mock数据配置 │ .webpackrc # webpack配置文件 │ package.json # 项目配置文件 ├─mock # 模拟数据文件 ├─public # 公共资源 └─src │ index.css # 主样式 │ index.js # 入口文件 │ router.js # 路由配置文件 ├─assets # 放置静态资源 ├─components # 组件 ├─models # dva最核心的内容 ├─routes # 路由页面 ├─services # 请求后台数据,调用后台API接口 └─utils # 工具类,默认放置了封装fetch的方法
Dva的核心概念
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)。
Dva解决了react没有解决的问题
- 通信:组件之间如何通信?
- 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等
通信问题
组件会发生三种通信。
- 向子组件发消息
- 向父组件发消息
- 向其他组件发消息
React 只提供了一种通信手段:传参。对于大应用,很不方便。
数据流问题
目前流行的数据流方案有:
- Flux,单向数据流方案,以 Redux 为代表
- Reactive,响应式数据流方案,以 Mobx 为代表
- 其他,比如 rxjs 等
最流行的社区 React 应用架构方案如下:
- 路由:React-Router
- 架构:Redux
- 异步操作:Redux-saga
这种方案虽然说可以达到所需要的目的,但是缺点也显而易见,要引入多个库,项目结构复杂。
那么dva所做的事情就是把他们进行了整合与封装,只暴露出几个简单的API,使得react项目的开发变得方便快捷。
在index.js中,可以看到dva执行的步骤
import dva from 'dva'; import './index.css'; // 1. Initialize const app = dva({}); // 2. Plugins // app.use({}); // 3. Model app.model(require('./models/example').default); // 4. Router app.router(require('./router').default); // 5. Start app.start('#root');
Model
首先看一下Model,这是dva的核心模块,组件之前的数据状态管理全都要靠它,通过Model可以解决react各组件间的传值问题。
一个Model就是一个对象,它包含以下属性:
- namespace: 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成
- state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
- reducers: Action 处理器,处理同步动作,用来算出最新的 State
- effects:Action 处理器,处理异步动作
reducers中包含多个Action 处理器,它接受两个参数,一个是当前的state,另一个是传入的参数,返回处理后的state来更新当前的state。
reducers: { delete(state, { payload: id }) { return state.filter(item => item.id !== id); }, },
Effect 是一个generator函数,内部使用yield关键字,标识每一步的操作(不管是同步还是异步),dva 提供多个 effect 函数内部的处理函数,比较常用的是 call 和 put。 call 用来执行异步操作, put用来发出一个Action,类似于 dispatch。就像下面这样:
effects:{ *deleteItem(action, { call, put}){ yield new Promise(resolve => { setTimeout(() => { resolve() }, 1000); }) yield put({ type: 'delete', payload: action.payload }) } }
State只是一个存储数据的地方,而数据的表现还是要在UI层进行呈现,还要可以实现用户操作UI层改变State的目的,State改变后也会引起View的改变。这就需要将State与View关联起来。
dva提供了一个叫connect的方法,connect 方法传入的第一个参数是 mapStateToProps 函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。它返回的也是一个 React 组件,通常称为容器组件。
const Products = ({ dispatch, products }) => { function handleDelete(id) { dispatch({ type: 'products/deleteItem', payload: id, }); } return ( <div> <h2>List of Products</h2> <ProductList onDelete={handleDelete} products={products} /> </div> ); }; export default connect(({ products }) => ({ products, }))(Products);
在handleDelete中有一个dispatch方法,被 connect 的 Component 会自动在 props 中拥有 dispatch 方法,它就是用来改变state的方法。其中的type的值是以“namespace/effect或reducer”的形式,payload为传入的额外参数。
dva的其他输出文件
dva/router
定义路由,默认输出 react-router接口, react-router-redux的接口通过属性 routerRedux 输出。
import { Router, Route, routerRedux } from 'dva/router';
dva/fetch
异步请求库,输出 isomorphic-fetch的接口。不和 dva 强绑定,可以选择任意的请求库。
import fetch from 'dva/fetch'; function parseJSON(response) { return response.json(); } function checkStatus(response) { if (response.status >= 200 && response.status < 300) { return response; } const error = new Error(response.statusText); error.response = response; throw error; } export default function request(url, options) { return fetch(url, options) .then(checkStatus) .then(parseJSON) .then(data => ({ data })) .catch(err => ({ err })); }
dva/saga
输出 redux-saga的接口,主要用于用例的编写。(用例中需要用到 effects)
dva/dynamic
解决组件动态加载问题的 util 方法。
在实际项目开发中,我们的model数量是很多的,每个model一般都只是在对应的几个模块中会用到,但在dva的初始化过程中,我们就必须把所有要用的model加载进来,这显然对性能方面是很不友好的。如果能在对应的路由下只加载所用到的model,这将会得到很大的提升。
import dynamic from 'dva/dynamic'; const UserPageComponent = dynamic({ app, models: () => [ import('./models/users'), ], component: () => import('./routes/UserPage'), });
- app: dva 实例,加载 models 时需要
- models: 返回 Promise 数组的函数,Promise 返回 dva model
- component:返回 Promise 的函数,Promise 返回 React Component
插件机制
dva中可以灵活的配置 hooks 或者注册插件,如dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
import createLoading from 'dva-loading'; ... app.use(createLoading(opts)); app = dva(opts)
app = dva(opts)
opts中可以设置 history 和初始化 state 数据,这里的 state 优先级要高于model中的state。
const app = dva({ history: createHistory(), initialState: { //初始数据 products: [ { name: 'dva', id: 1 }, { name: 'antd', id: 2 }, ], }, });
还可以在这里面设置以下hooks:
onError((err, dispatch) => {})
effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态。如果我们用 antd,那么最简单的全局错误处理通常会这么做:
import { message } from 'antd'; const app = dva({ onError(e) { message.error(e.message, /* duration */3); }, });
onAction(fn | fn[])
在 action 被 dispatch 时触发,用于注册 redux 中间件。支持函数或函数数组格式。例如我们要通过 redux-logger打印日志:
import createLogger from 'redux-logger'; const app = dva({ onAction: createLogger(opts), });
onStateChange(fn)
state 改变时触发,可用于同步 state 到 localStorage,服务器端等。
onReducer(fn)
封装 reducer 执行。比如借助 redux-undo实现 redo/undo :
import undoable from 'redux-undo'; const app = dva({ onReducer: reducer => { return (state, action) => { const undoOpts = {}; const newState = undoable(reducer, undoOpts)(state, action); // 由于 dva 同步了 routing 数据,所以需要把这部分还原 return { ...newState, routing: newState.present.routing }; }, }, });
onEffect(fn)
封装 effect 执行。比如 dva-loading基于此实现了自动处理 loading 状态。
onHmr(fn)
热替换相关,目前用于 babel-plugin-dva-hmr。
extraReducers
指定额外的 reducer,比如 redux-form需要指定额外的 form reducer:
import { reducer as formReducer } from 'redux-form' const app = dva({ extraReducers: { form: formReducer, }, });
extraEnhancers
指定额外的 StoreEnhancer,比如结合 redux-persist的使用:
import { persistStore, autoRehydrate } from 'redux-persist'; const app = dva({ extraEnhancers: [autoRehydrate()], }); persistStore(app._store);
欢迎访问我的个人网站:www.dengzhanyong.com
喜欢的话可以关注我的公众号:【前端筱园】
一起交流,共同成长
- 初学单片机几个不易掌握的概念
- 初学单片机几个不易掌握的概念
- tl01-GIT基本概念与核心命令掌握
- 数据结构 学习笔记之:静态链表--史上最简单的C语言实现——只为掌握概念——不清楚静态链表的鸟鸟们有福了!
- 初学java--利用面向对象概念做一个简单的新闻系统
- 初学单片机几个不易掌握的概念
- 初学数据仓库--简单概念
- Elasticsearch简单使用系列--详细介绍ES的核心概念
- hibernate学习之简单核心概念
- 掌握 javascript 核心概念 最好的教程 系列 之一
- Maven应该掌握的五个核心概念
- 初学51单片机几个不易掌握的概念
- Git基本概念与核心命令掌握
- Java初学入门需掌握的30个概念
- 学习大数据必须掌握七大核心技术概念
- 初学51单片机几个不易掌握的概念
- 黑马程序员_并发编程笔记_初学简单概念
- 聊天室系列01:基本概念准备,掌握InetAddress、InetSocketAddress、URL类的简单使用
- 抖音如何上热门!掌握了技巧原来如此简单!
- 初学入门需掌握的30个基本概念