爬坑之阿里dva的使用

1,417 阅读3分钟

最近在做react的项目先是阿里云的oss,再是支付宝的ant design,再次要吐槽一些ant design的upload组件对于oss上传的支持真的时糟心。之后项目的状态管理要用到dva,也是今天的爬坑对象。

why dva and what's dva

当我们在学习react的时候都是react+redux的模式开始,但是redux的学习成本有点高。redux的核心时store、action和reducer。现在处理异步要用到react-saga。在写redux时需要在reducer、sage、action、action之间来回切换,后期state难以维护,不便于组织业务模型,还有就是sage书写太复杂,每监听一个 action 都需要走 fork -> watcher -> worker 的流程。

avatar

dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。( Inspired by elm and choo. )

dva 是 framework,不是 library,类似 emberjs,会很明确地告诉你每个部件应该怎么写,这对于团队而言,会更可控。另外,除了 react 和 react-dom 是 peerDependencies 以外,dva 封装了所有其他依赖。

dva 实现上尽量不创建新语法,而是用依赖库本身的语法,比如 router 的定义还是用 react-router 的 JSX 语法的方式(dynamic config 是性能的考虑层面,之后会支持)。

avatar

特性

  • 易学易用: 仅有6个api,对redux用户友好
  • elm概念: 通过 reducers, effects, subscriptions组织model
  • 支持mobile和react-native: 跨平台
  • 支持 HMR:目前基于 babel-plugin-dva-hmr支持 components、routers和models的HMR
  • 动态加载Model和路由:按需加载加快访问速度
  • 插件机制
  • 完善的语法分析库 dva-ast: dva-cli
  • 支持TypeScript

dva 的五部曲

import './index.html';
import './index.css';
import dva from 'dva';

// 1. Initialize
const app = dva();

// 2. Plugins - 该项为选择项
//app.use({});

// 3. Model 的注册
//app.model(require('./models/example'));

// 4. 配置 Router
app.router(require('./router'));

// 5. Start
app.start('#root');

dva文档还有一步

const App = connect(mapStateToProps)(Component)

核心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} })
        }
      })
    }
  }
}

注册

在router.js中注册namespace

import React from 'react'
import PropTypes from 'prop-types'
import { Router, Route, IndexRoute, IndexRedirect } from 'dva/router'

import App from './routes/App'

export default function ({ history, app }) {
    reginsterModel(app, require('./models/todo'))
    return (
        <Router history = {history}>
            <Route path="index"></Route>
        </Router>
    )
}

在组件中使用

import React, {PureComponent} from 'react'
import {connect} from 'dva'
import {routerRedux} from 'dva/router'
import {Button} from 'antd'

const namespace = 'todo'
function delete () {
    dispatch({
        type: `${namespace}/delete`
    })
}

class Index extends PureComponent {
    static PropTypes = {
        todo: PropTypes.object.isRequired,
    }
    
    render() {
        return(
            <Button onClick={this.delete()}>click</Button>
        )
    }
}

function mapStateToProps({todo}){
    return {todo}
}

export default connect(mapStateToProps) (Index)

还有更加详细的用法请参考 dev文档