资讯专栏INFORMATION COLUMN

dva框架使用详解及Demo教程

bergwhite / 2502人阅读

摘要:框架的使用详解及教程在前段时间,我们也学习讲解过框架的基本使用,但是有很多同学在交流群里给我的反馈信息说,框架理解上有难度,看了之后还是一脸懵逼不知道如何下手,很多同学就转向选择使用框架。

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

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

Demo运行效果图

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

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 包含如下配置:

history:指定给路由用的 history,默认是 hashHistory

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的讲解: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 (
    
{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();

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

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

dva将action reducer 封装到model 中,异步流程采用Generator处理

总结

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

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

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

福利时间

作者React Native开源项目OneM地址(按照企业开发标准搭建框架完成开发的):https://github.com/guangqiang-liu/OneM:欢迎小伙伴们 star

作者简书主页:包含60多篇RN开发相关的技术文章http://www.jianshu.com/u/023338566ca5 欢迎小伙伴们:多多关注多多点赞

作者React Native QQ技术交流群:620792950 欢迎小伙伴进群交流学习

友情提示:在开发中有遇到RN相关的技术问题,欢迎小伙伴加入交流群(620792950),在群里提问、互相交流学习。交流群也定期更新最新的RN学习资料给大家,谢谢大家支持!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97453.html

相关文章

  • redux-saga框架使用详解Demo教程

    摘要:通过创建将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替中间件。 redux-saga框架使用详解及Demo教程 前面我们讲解过redux框架和dva框架的基本使用,因为dva框架中effects模块设计到了redux-saga中的知识点,可能有的同学们会用dva框架,但是对redux-saga又不是很熟悉,今天我们就来简单的讲解下saga框架的主要API和如何配合redux框...

    Nosee 评论0 收藏0
  • Taro 优秀学习资源汇总

    摘要:多端统一开发框架优秀学习资源汇总官方资源项目仓库官方文档项目仓库官方文档微信小程序官方文档百度智能小程序官方文档支付宝小程序官方文档字节跳动小程序官方文档文章教程不敢阅读包源码带你揭秘背后的哲学从到构建适配不同端微信小程序等的应用小程序最 Awesome Taro 多端统一开发框架 Taro 优秀学习资源汇总 showImg(https://segmentfault.com/img/r...

    toddmark 评论0 收藏0
  • 平时积累的前端资源,持续更新中。。。

    本文收集学习过程中使用到的资源。 持续更新中…… 项目地址 https://github.com/abc-club/f... 目录 vue react react-native Weex typescript Taro nodejs 常用库 css js es6 移动端 微信公众号 小程序 webpack GraphQL 性能与监控 高质文章 趋势 动效 数据结构与算法 js core 代码规范...

    acrazing 评论0 收藏0
  • ? 基于 react + react-router + redux 构建的移动端微应用

    摘要:项目地址基于构建的移动端单页微应用,适合于核心概念的理解与掌握。前言这个项目是介于版本之后并且完全仿照功能设计的版本。但随之而来通常会有这样的疑问概念太多,并且都是分离的分文件。 react-mobile-starter 项目地址 基于 react + redux + react-router 构建的移动端单页微应用,适合于react、redux、react-router核心概念的理解...

    zhangke3016 评论0 收藏0

发表评论

0条评论

bergwhite

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<