dva 从入门到入土

dva 是对 redux 的一层浅封装,基于 react 语言,实现前端代码的分层

一般会分为3层:

  • component:组件渲染、展示页面
  • models:数据组装、处理
  • services:接口调用、拿数据

以 smart-ui 项目中用户列表模块为例:


smart-ui

1. router.js 文件中配置路由,指定具体路径所要加载的 views 和 models

{
    path: '/security/users',
    models: () => [import('./models/security/user')],
    component: () => import('./views/security/user/'),
}

2. services

.roadhogrc.js文件中配置好代理请求地址

proxy: {
    "/api/v1/weather": {
      "target": "https://api.seniverse.com/",
      "changeOrigin": true,
      "pathRewrite": { "^/api/v1/weather" : "/v3/weather" }
    },
    "/services": {
        "target": "http://ip地址:8080/",
        "changeOrigin": true,
        "pathRewrite": { "^/services" : "/services" }
      },
  },

serviecs/user.js中定义好接口,获取原始数据。项目中,已对常用的增删改查做了一层封装(包括分页).
对于自定义方法,提供 url、method、data(可选),返回 request 封装好的 http 请求

export async function query({page = 1 , pageSize = 10 , ...qs}) {
    return user.listPage(page,pageSize,qs)
}

export async function get({id}) {
    return user.get(id)
}

export async function create (params) {
    return user.create(params);
}

export async function update (params) {
    return user.update(params);
}

// 用户设置 自定义方法
//修改用户
export async function modify(payload) {
    return request({
        url: `${apiPrefix}/security/user/modify`,
        method: 'put',
        data: payload
    })
}

3. models

import pathToRegexp from 'path-to-regexp'
import modelExtend from 'dva-model-extend'
import * as user from 'services/security/user'
import { pageModel } from 'models/common'
import { message } from 'antd'

export default modelExtend(pageModel, {

  namespace: 'secUser',

  state: {
   currentItem: {},
   modalVisible: false,
   modalType: 'create',
   selectedRowKeys: [],
  },
  
  // 添加一个监听器,当pathname === '/security/users'时执行dispatch
  subscriptions: {
    setup ({ dispatch, history }) {
      history.listen((location) => {
        if (location.pathname === '/security/users') {
          dispatch({ 
              type: 'query', 
              payload: location.query || {},
          })
        }
      })
    },
  },

// 
  effects: {
    * query ({
      payload,
    }, { call, put }) {
      const result = yield call(user.query, payload)
      const { success, message, status, data } = result
      if (success) {
        yield put({
          type: 'querySuccess',
          payload: data,
        })
      } else {
        throw result
      }
    },

    * create ({ payload }, { call, put,select }) {
      const {data} = yield call(user.create, payload)
      message.info("新增成功");   
      yield put({ type: 'hideModal' })
      const { pagination: { pageSize,current } } = yield select(_ => _.secUser)
      yield put({ type: 'query', payload: {pageSize, page:current } })
    },

    * update ({ payload }, { select, call, put }) {
      const id = yield select(({ secUser }) => secUser.currentItem.id)
      const newUser = { ...payload, id }
      const {data} = yield call(user.update, newUser)
      message.info("修改成功");
      yield put({ type: 'hideModal' })
      const { pagination: { pageSize,current } } = yield select(_ => _.secUser)
      yield put({ type: 'query', payload: {pageSize, page:current } })
    },
  },

  reducers: {
     showModal (state, { payload }) {
      return { ...state, ...payload, modalVisible: true }
    },

    hideModal (state) {
      return { ...state, modalVisible: false }
    },
  },
})
  • query、update 前面的 * 号,表示这个方法是一个 Generator函数
  • subscriptions:用于添加一个监听器
  • effects:异步执行 dispatch,用于发起异步请求
    • call 是调用执行一个函数 (调用service里面的方法)
    • put 则是相当于 dispatch 执行一个 action
    • select 则可以用来访问其它 model
  • reducers:同步请求,主要用来改变state

4.component

注意:组件入口文件一定要命名为 index.js ,否则会找不到

import React from 'react'
import PropTypes from 'prop-types'
import { routerRedux } from 'dva/router'
import { connect } from 'dva'
import { Row, Col, Button, Popconfirm } from 'antd'
import List from './List'
import Filter from './Filter'
import Modal from './Modal'
import { Page } from 'components'
import { i18n }  from 'utils'

const User = ({ location, dispatch, secUser, loading }) => {
  const { dataSource, pagination, currentItem, modalVisible, modalType, selectedRowKeys } = secUser
  const { pageSize } = 10

  const modalProps = {
    item: modalType === 'create' ? {} : currentItem,
    visible: modalVisible,
    maskClosable: false,
    confirmLoading: loading.effects['secUser/update'],
    title: modalType === 'create' ? i18n('lab.user.create') : i18n('lab.user.update'),
    wrapClassName: 'vertical-center-modal',
    onOk (data) {
      dispatch({
        type: `secUser/${modalType}`,
        payload: data,
      })
    },
    onCancel () {
      dispatch({
        type: 'secUser/hideModal',
      })
    },
  }

  const listProps = {
    dataSource,
    loading: loading.effects['secUser/query'],
    pagination,
    location,
    onChange (page) {
      const { query, pathname } = location
      dispatch(routerRedux.push({
        pathname,
        query: {
          ...query,
          page: page.current,
          pageSize: page.pageSize,
        },
      }))
    },
    onDeleteItem (id) {
      dispatch({
        type: 'secUser/delete',
        payload: id,
      })
    },
    onEditItem (item) {
      dispatch({
        type: 'secUser/showModal',
        payload: {
          modalType: 'update',
          currentItem: item,
        },
      })
    },
    rowSelection: {
      selectedRowKeys,
      onChange: (keys) => {
        dispatch({
          type: 'secUser/updateState',
          payload: {
            selectedRowKeys: keys,
          },
        })
      },
    },
  }

  const filterProps = {
    ...
  }

  const handleDeleteItems = () => {
    dispatch({
      type: 'secUser/multiDelete',
      payload: {
        ids: selectedRowKeys,
      },
    })
  }

  return (
    <Page inner>
      <Filter {...filterProps} />
      <List {...listProps} />
      {modalVisible && <Modal {...modalProps} />}
    </Page>
  )
}

User.propTypes = {
  secUser: PropTypes.object,
  location: PropTypes.object,
  dispatch: PropTypes.func,
  loading: PropTypes.object,
}

export default connect(({ secUser }) => ({ secUser}))(User)
  • connect方法将组件与数据关联在一起
    export default connect(({ secUser }) => ({ secUser}))(User)
  • secUser 必须与对应 models 里面定义的 namespace 字段一致
  • 组件中发起请求调用 dispatch
    • type 字段对应 models 中 effects 和 reducers 对应方法
    • payload 为传参对象
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容