dva框架的使用详解及Demo教程

在前段时间,我们也学习讲解过Redux框架的基本使用,但是有很多同学在交流群里给我的反馈信息说,redux框架理解上有难度,看了之后还是一脸懵逼不知道如何下手,很多同学就转向选择使用dva框架。其实dva框架就是一个redux框架与redux-saga等框架的一个集大成者,把几个常用的数据处理框架进行了再次封装,在使用方式上给使用者带来了便利,下面我们就来简单的介绍下dva框架的基本API和基本使用

Demo运行效果图

这里和讲解Redux框架一样,作者任然是提供了两个经典的Demo示例,CounterApp 和 TodoList 来帮助初学者更好的理解和使用

http://ovyjkveav.bkt.clouddn.com/17-12-22/25369015.jpg

http://ovyjkveav.bkt.clouddn.com/17-12-22/35513550.jpg

Demo地址

  • CounterApp

https://github.com/guangqiang-liu/react-dva-counter

  • TodoList

https://github.com/guangqiang-liu/react-dva-todoList

dva的由来

D.Va拥有一部强大的机甲,它具有两台全自动的近距离聚变机炮、可以使机甲飞跃敌人或障碍物的推进器、 还有可以抵御来自正面的远程攻击的防御矩阵。—— 来自 守望先锋 。

dva 官方地址

https://github.com/dvajs/dva/blob/master/README_zh-CN.md

dva核心API

  • app = dva(opts)

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

opts 包含如下配置:

  1. history:指定给路由用的 history,默认是 hashHistory
  2. initialState:指定初始数据,优先级高于 model 中的 state,默认是 {}

如果配置history 为 browserHistory,则创建dva对象可以写成如下写法

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

另外,出于易用性的考虑,opts 里也可以配所有的 hooks ,下面包含全部的可配属性:

const app = dva({
history,
initialState,
onError,
onAction,
onStateChange,
onReducer,
onEffect,
onHmr,
extraReducers,
extraEnhancers,
})
  • app.use(hooks)

配置 hooks 或者注册插件。(插件最终返回的是 hooks )

比如注册 dva-loading 插件的例子:

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

hooks包含如下配置项:

1、 onError((err, dispatch) => {})

effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态

注意:subscription 并没有加 try...catch,所以有错误时需通过第二个参数 done 主动抛错

例子:

app.model({
subscriptions: {
setup({ dispatch }, done) {
done(e)
},
},
})

如果我们使用antd组件,那么最简单的全局错误处理通常会这么做:

import { message } from 'antd'
const app = dva({
onError(e) {
message.error(e.message, 3)
},
})

2、 onAction(fn | fn[])

在action被dispatch时触发,用于注册 redux 中间件。支持函数或函数数组格式

例如我们要通过 redux-logger 打印日志:

import createLogger from 'redux-logger';
const app = dva({
onAction: createLogger(opts),
})

3、 onStateChange(fn)

state 改变时触发,可用于同步 state 到 localStorage,服务器端等

4、 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 };
},
},
})

5、 onEffect(fn)

封装 effect 执行。比如 dva-loading 基于此实现了自动处理 loading 状态

6、 onHmr(fn)

热替换相关,目前用于 babel-plugin-dva-hmr

7、 extraReducers

指定额外的 reducer,比如 redux-form 需要指定额外的 form reducer:

import { reducer as formReducer } from 'redux-form'
const app = dva({
extraReducers: {
form: formReducer,
},
})
  • app.model(model)

注册model,这个操作时dva中核心操作,下面单独做详解

  • app.unmodel(namespace)

取消 model 注册,清理 reducers, effects 和 subscriptions。subscription 如果没有返回 unlisten 函数,使用 app.unmodel 会给予警告⚠️

  • app.router(({ history, app }) => RouterConfig)

注册路由表,这一操作步骤在dva中也很重要

// 注册路由
app.router(require('./router'))
// 路由文件
import { Router, Route } from 'dva/router';
import IndexPage from './routes/IndexPage'
import TodoList from './routes/TodoList' function RouterConfig({ history }) {
return (
<Router history={history}>
<Route path="/" component={IndexPage} />
<Route path='/todoList' components={TodoList}/>
</Router>
)
}
export default RouterConfig

当然,如果我们想解决组件动态加载问题,我们的路由文件也可以按照下面的写法来写

import { Router, Switch, Route } from 'dva/router'
import dynamic from 'dva/dynamic' function RouterConfig({ history, app }) {
const IndexPage = dynamic({
app,
component: () => import('./routes/IndexPage'),
}) const Users = dynamic({
app,
models: () => [import('./models/users')],
component: () => import('./routes/Users'),
}) return (
<Router history={history}>
<Switch>
<Route exact path="/" component={IndexPage} />
<Route exact path="/users" component={Users} />
</Switch>
</Router>
)
} export default RouterConfig

其中dynamic(opts) 中opt包含三个配置项:

  • opts

    • app: dva 实例,加载 models 时需要
    • models: 返回 Promise 数组的函数,Promise 返回 dva model
    • component:返回 Promise 的函数,Promise 返回 React Component
  • app.start(selector?)

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

app.start('#root')

那么什么时候不加 selector?常见场景有测试、node端、react-native 和 i18n 国际化支持

比如通过 react-intl 支持国际化的例子:

import { IntlProvider } from 'react-intl'
...
const App = app.start()
ReactDOM.render(<IntlProvider><App /></IntlProvider>, htmlElement)

dva框架中的核心层:Model

下面是简单常规的 model 文件的写法

/** Created by guangqiang on 2017/12/17. */

import queryString from 'query-string'
import * as todoService from '../services/todo' export default {
namespace: 'todo',
state: {
list: []
},
reducers: {
save(state, { payload: { list } }) {
return { ...state, list }
}
},
effects: {
*addTodo({ payload: value }, { call, put, select }) {
// 模拟网络请求
const data = yield call(todoService.query, value)
console.log(data)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
const tempObj = {}
tempObj.title = value
tempObj.id = list.length
tempObj.finished = false
list.push(tempObj)
yield put({ type: 'save', payload: { list }})
},
*toggle({ payload: index }, { call, put, select }) {
// 模拟网络请求
const data = yield call(todoService.query, index)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
let obj = list[index]
obj.finished = !obj.finished
yield put({ type: 'save', payload: { list } })
},
*delete({ payload: index }, { call, put, select }) {
const data = yield call(todoService.query, index)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
list.splice(index, 1)
yield put({ type: 'save', payload: { list } })
},
*modify({ payload: { value, index } }, { call, put, select }) {
const data = yield call(todoService.query, value)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
let obj = list[index]
obj.title = value
yield put({ type: 'save', payload: { list } })
}
},
subscriptions: {
setup({ dispatch, history }) {
// 监听路由的变化,请求页面数据
return history.listen(({ pathname, search }) => {
const query = queryString.parse(search)
let list = []
if (pathname === 'todoList') {
dispatch({ type: 'save', payload: {list} })
}
})
}
}
}

model对象中包含5个重要的属性:

  • namespace

model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过.的方式创建多层命名空间

  • state

reducer的初始值,优先级低于传给dva()的 opts.initialState

例如:

const app = dva({
initialState: { count: 1 },
});
app.model({
namespace: 'count',
state: 0,
})

此时,在 app.start() 后 state.count 为 1

  • reducers

以 key/value 格式定义reducer,用于处理同步操作,唯一可以修改 state 的地方,由 action 触发

格式为 (state, action) => newState 或 [(state, action) => newState, enhancer]

namespace: 'todo',
state: {
list: []
},
// reducers 写法
reducers: {
save(state, { payload: { list } }) {
return { ...state, list }
}
}
  • effects

以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state。由action 触发,可以触发action,可以和服务器交互,可以获取全局 state 的数据等等

注意: dva框架中的effects 模块的设计思想来源于 redux-saga 框架,如果同学们对 redux-saga框架不熟悉,可以查看作者对 redux-saga的讲解:https://www.jianshu.com/p/7cac18e8d870

格式为 *(action, effects) => void 或 [*(action, effects) => void, { type }]

type 类型有有如下四种:

1、takeEvery

2、takeLatest

3、throttle

4、watcher

// effects 写法
effects: {
*addTodo({ payload: value }, { call, put, select }) {
// 模拟网络请求
const data = yield call(todoService.query, value)
console.log(data)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
const tempObj = {}
tempObj.title = value
tempObj.id = list.length
tempObj.finished = false
list.push(tempObj)
yield put({ type: 'save', payload: { list }})
},
*toggle({ payload: index }, { call, put, select }) {
// 模拟网络请求
const data = yield call(todoService.query, index)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
let obj = list[index]
obj.finished = !obj.finished
yield put({ type: 'save', payload: { list } })
},
*delete({ payload: index }, { call, put, select }) {
const data = yield call(todoService.query, index)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
list.splice(index, 1)
yield put({ type: 'save', payload: { list } })
},
*modify({ payload: { value, index } }, { call, put, select }) {
const data = yield call(todoService.query, value)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
let obj = list[index]
obj.title = value
yield put({ type: 'save', payload: { list } })
}
}
  • subscriptions

以 key/value 格式定义 subscription,subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action

在 app.start() 时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等

格式为 ({ dispatch, history }, done) => unlistenFunction

注意:如果要使用 app.unmodel(),subscription 必须返回 unlisten 方法,用于取消数据订阅

// subscriptions 写法
subscriptions: {
setup({ dispatch, history }) {
// 监听路由的变化,请求页面数据
return history.listen(({ pathname, search }) => {
const query = queryString.parse(search)
let list = []
if (pathname === 'todoList') {
dispatch({ type: 'save', payload: {list} })
}
})
}
}

使用dva框架和直接使用redux写法的区别

  • 使用 redux

actions.js 文件

export const REQUEST_TODO = 'REQUEST_TODO';
export const RESPONSE_TODO = 'RESPONSE_TODO'; const request = count => ({type: REQUEST_TODO, payload: {loading: true, count}}); const response = count => ({type: RESPONSE_TODO, payload: {loading: false, count}}); export const fetch = count => {
return (dispatch) => {
dispatch(request(count)); return new Promise(resolve => {
setTimeout(() => {
resolve(count + 1);
}, 1000)
}).then(data => {
dispatch(response(data))
})
}
}

reducer.js 文件

import { REQUEST_TODO, RESPONSE_TODO } from './actions';

export default (state = {
loading: false,
count: 0
}, action) => {
switch (action.type) {
case REQUEST_TODO:
return {...state, ...action.payload};
case RESPONSE_TODO:
return {...state, ...action.payload};
default:
return state;
}
}

app.js 文件

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import * as actions from './actions'; const App = ({fetch, count, loading}) => {
return (
<div>
{loading ? <div>loading...</div> : <div>{count}</div>}
<button onClick={() => fetch(count)}>add</button>
</div>
)
} function mapStateToProps(state) {
return state;
} function mapDispatchToProps(dispatch) {
return bindActionCreators(actions, dispatch)
} export default connect(mapStateToProps, mapDispatchToProps)(App)

index.js 文件

import { render } from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'; import reducer from './app/reducer';
import App from './app/app'; const store = createStore(reducer, applyMiddleware(thunkMiddleware)); render(
<Provider store={store}>
<App/>
</Provider>
,
document.getElementById('app')
)
  • 使用dva

model.js 文件

export default {
namespace: 'demo',
state: {
loading: false,
count: 0
},
reducers: {
request(state, payload) {
return {...state, ...payload};
},
response(state, payload) {
return {...state, ...payload};
}
},
effects: {
*'fetch'(action, {put, call}) {
yield put({type: 'request', loading: true}); let count = yield call((count) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(count + 1);
}, 1000);
});
}, action.count); yield put({
type: 'response',
loading: false,
count
});
}
}
}

app.js 文件

import React from 'react'
import { connect } from 'dva'; const App = ({fetch, count, loading}) => {
return (
<div>
{loading ? <div>loading...</div> : <div>{count}</div>}
<button onClick={() => fetch(count)}>add</button>
</div>
)
} function mapStateToProps(state) {
return state.demo;
} function mapDispatchToProps(dispatch) {
return {
fetch(count){
dispatch({type: 'demo/fetch', count});
}
}
} export default connect(mapStateToProps, mapDispatchToProps)(App)

index.js 文件

import dva from 'dva';
import model from './model';
import App from './app'; const app = dva(); app.use({}); app.model(model); app.router(() => <App />); app.start();

我们通过上面两种不同方式来实现一个异步的计数器的代码结构发现:

  1. 使用 redux 需要拆分出action模块和reducer模块

  2. dva将actionreducer封装到model中,异步流程采用Generator处理

总结

本篇文章主要讲解了dva框架中开发常用API和一些使用技巧,如果想查看更多更全面的API,请参照dva官方文档:https://github.com/dvajs/dva

如果同学们看完教程还是不知道如何使用dva框架,建议运行作者提供的Demo示例结合学习

作者建议:同学们可以将作者之前讲解的redux框架和dva框架对比来学习理解,这样更清楚他们之间的区别和联系。

更多文章

  • 作者React Native开源项目OneM【500+ star】地址(按照企业开发标准搭建框架完成开发的):https://github.com/guangqiang-liu/OneM:欢迎小伙伴们 star
  • 作者简书主页:包含60多篇RN开发相关的技术文章http://www.jianshu.com/u/023338566ca5欢迎小伙伴们:多多关注,多多点赞
  • 作者React Native QQ技术交流群:620792950 欢迎小伙伴进群交流学习
  • 友情提示:在开发中有遇到RN相关的技术问题,欢迎小伙伴加入交流群(620792950),在群里提问、互相交流学习。交流群也定期更新最新的RN学习资料给大家,谢谢大家支持!

小伙伴们扫下方二维码加入RN技术交流QQ群

QQ群二维码,500+ RN工程师在等你加入哦

dva框架使用详解及Demo教程的更多相关文章

  1. redux-saga框架使用详解及Demo教程

    redux-saga框架使用详解及Demo教程 前面我们讲解过redux框架和dva框架的基本使用,因为dva框架中effects模块设计到了redux-saga中的知识点,可能有的同学们会用dva框 ...

  2. 【python3+request】python3+requests接口自动化测试框架实例详解教程

    转自:https://my.oschina.net/u/3041656/blog/820023 [python3+request]python3+requests接口自动化测试框架实例详解教程 前段时 ...

  3. python+requests接口自动化测试框架实例详解

    python+requests接口自动化测试框架实例详解   转自https://my.oschina.net/u/3041656/blog/820023 摘要: python + requests实 ...

  4. Hadoop 新 MapReduce 框架 Yarn 详解

    Hadoop 新 MapReduce 框架 Yarn 详解: http://www.ibm.com/developerworks/cn/opensource/os-cn-hadoop-yarn/ Ap ...

  5. 测试框架mochajs详解

    测试框架mochajs详解 章节目录 关于单元测试的想法 mocha单元测试框架简介 安装mocha 一个简单的例子 mocha支持的断言模块 同步代码测试 异步代码测试 promise代码测试 不建 ...

  6. 转: javascript模块加载框架seajs详解

    javascript模块加载框架seajs详解 SeaJS是一个遵循commonJS规范的javascript模块加载框架,可以实现javascript的模块化开发和模块化加载(模块可按需加载或全部加 ...

  7. Android热门网络框架Volley详解[申明:来源于网络]

    Android热门网络框架Volley详解[申明:来源于网络] 地址:http://www.cnblogs.com/caobotao/p/5071658.html

  8. laravel5.5的任务调度(定时任务)详解(demo)

    https://blog.csdn.net/LJFPHP/article/details/80417552 laravel5.5的定时任务详解(demo) 这篇文章写得挺详细的.看了它我基本就会用了 ...

  9. openlayers3教材详解及demo(完整)

            openlayers3教材详解及demo(完整)        OpenLayers 3对OpenLayers网络地图库进行了根本的重新设计.版本2虽然被广泛使用,但从JavaScri ...

随机推荐

  1. rocketmq 两个线程同时消费一个消息

    1.问题描述 线上项目A部署两台机器,每台机器两个实例,订阅同一个topic,消费心跳数据. (两台机器host1,host2) 运维同事 部署时 有一个实例用root账户重启的, 然后该实例出现两个 ...

  2. JavaScript练习笔记整理·4 - 6.26

    基础练习(1): 我的解答为: function getMiddle(s) { if(s.length%2 == 0) { return s.charAt(s.length/2-1)+s.charAt ...

  3. Linux基础之-利用shell脚本实现自动监控系统服务

    目的:监控集群内nginx及nfs服务运行是否正常,如任一服务异常,则发送邮件通知用户 条件: 1. 主机及子机IP地址,hostname已确定: 2. 主机与子机能够免密通讯,即基于密匙通讯(相关命 ...

  4. codeforces之始

    很早就听说acmer界的CF嘞!还记得刚开始听到神犇们在讨论CF的时候我还以为是网游CF(穿越火线)呢... 今年刚开学的时候就打算开始打cf的,由于一些事情耽搁了.之后又要准备省赛所以就一直拖到现在 ...

  5. 佣金百万so easy!阿里云推广联盟喊你来赚钱

    淘客速来,佣金百万so easy!阿里云推广联盟喊你来赚钱 阿里云CPS推广阶梯返佣活动火热升级! 坐享15%佣金!一笔成交即有奖励!最高奖励2000元! 超高客单价.高转化率.高佣金! 招募淘客推广 ...

  6. Vue入门基础(火柴)

    前言 由于个人十分欣赏博友——小火柴的蓝色理想,他的博文我看了大多数,觉得十分的精彩,然而很多都是看后即忘.我想除了没有经常动手敲代码,更可能是在看的时候忽略了很多细节,因此打算把他的博文通通给“抄袭 ...

  7. mybatis支持的jdbc类型

    参考mybatis的枚举JdbcType源码: 关于这些类型mybatis是如何处理可以看一下TypeHandler类的源码.

  8. Android学习——BroadCast(一)

    初识广播 BroadCast即为广播,为安卓四大组件之一,用于在应用程序和Activity间传输信息.一条广播,分为发送和接收两部分,发送方通过Intent存储信息,并进行发送.接收方通过BroadC ...

  9. 设计一个缓存器 ReadLock提高性能

    /** * * @描述: 设计一个缓存器 ReadLock提高性能. * @作者: Wnj . * @创建时间: 2017年5月16日 . * @版本: 1.0 . */ public class C ...

  10. SQL Server ->> 与SQL Server服务配置相关的DMV

    1) sys.dm_server_services这个DMV可以告诉我们与当前版本的SQL Server相关的服务的启动状态和最后一次启动的时间,诸如这样的信息. SELECT * FROM sys. ...