您的位置:首页 > Web前端 > JavaScript

初学DvaJS,掌握核心概念后原来如此简单

2020-07-01 16:52 323 查看

钉钉、微博极速扩容黑科技,点击观看阿里云弹性计算年度发布会!>>>

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

喜欢的话可以关注我的公众号:【前端筱园】

一起交流,共同成长

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  dva redux