博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
dva框架使用详解及Demo教程
阅读量:4088 次
发布时间:2019-05-25

本文共 12533 字,大约阅读时间需要 41 分钟。

在前段时间,我们也学习讲解过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

  • TodoList

dva的由来

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

dva 官方地址

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 (    
)}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 (    
)}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(
, 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的讲解:

格式为 *(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 (    
{loading ?
loading...
:
{count}
}
)}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(  
, 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 (    
{loading ?
loading...
:
{count}
}
)}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.start();复制代码

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

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

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

总结

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

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

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

福利时间

  • 作者React Native开源项目OneM地址(按照企业开发标准搭建框架完成开发的)::欢迎小伙伴们 star

作者:光强
链接:https://juejin.im/post/5b8d4e106fb9a019e73f8d02
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的文章
我发觉,不管是弄ROS OPENCV T265二次开发 SDK开发 caffe PX4 都是用的C++
查看>>
ROS的安装(包含文字和视频教程,我的ROS安装教程以这篇为准)
查看>>
国内有个码云,gitee
查看>>
原来我之前一直用的APM固件....现在很多东西明白了。
查看>>
realsense-ros里里程计相关代码
查看>>
似乎写个ROS功能包并不难,你会订阅话题发布话题,加点逻辑处理,就可以写一些基础的ROS功能包了。
查看>>
if __name__ == ‘__main__‘:就是Python里的main函数,脚本从这里开始执行,如果没有main函数则从上到下顺序执行。
查看>>
PX4官方用户和开发手册的首页面是会给你选择英文和中文的
查看>>
网络协议栈我是不是可以这么理解,就是把你要发送的数据自动处理成TCPIP格式的消息发出去,这种底层的转换不需要你弄了。
查看>>
除了LwIP还有uIP
查看>>
《跟工程师学嵌入式开发》这本书最后的终极项目我反而觉得有说头
查看>>
博士的申请考核制
查看>>
那些硬件的初始化函数主要是在做些上什么?
查看>>
MAVLink学习之路05_MAVLink应用编程接口分析(也有讲STM32下的收发函数)
查看>>
找到了中文版的mavlink手册
查看>>
浅谈飞控开发的仿真功能
查看>>
我觉得在室内弄无人机开发装个防撞机架还是很有必要的,TBUS就做得很好。
查看>>
serial也是见到很多次了,似乎它就是一种串行通信协议
查看>>
TBUS的一些信息
查看>>
PX4+激光雷达在gazebo中仿真实现(古月居)
查看>>