因为积分商城项目接触dva搭建的项目,由于和以前使用vue框架不同,边完成需求,边学习框架,现对学习过程做一个记录,希望对后来接触dva的小伙伴有所帮助,有什么错误的地方,大家一起纠正。
first of all
照惯例介绍一下dva是什么
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
官方解释是这样,在我理解,dva是一个对react进行简单封装,更方便进行开发的react框架。
由于都是框架,我们大部分项目是vue进行的,可能小伙伴们对vue更加熟悉,在后续我也会根据vue进行对比,便于记忆。
1. 安装
dva也使用脚手架进行安装。
$ npm install dva-cli -g
是不是很熟悉,和vue类似,用法也和vue-cli类似
2. 创建项目
$ dva new dva-quickstart
使用new
3. 启动
$ cd dva-quickstart
$ npm start
本地就跑起来了
4. 安装antd
在使用时我们搭配antd开发,antd为react的ui框架,有了ui框架开发起来更加方便快捷
$ npm install antd babel-plugin-import --save
注意,如果node版本高于5.4.0会出现报错
Snipaste_2019-04-13_15-27-41.png
解决办法:
1.node降版本
2.删除node_modules文件夹
我是node5.6.0,所以
$ npm install antd babel-plugin-import --save
$ npm install
编辑 .webpackrc,使 babel-plugin-import 插件生效。
{
+ "extraBabelPlugins": [
+ ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
+ ]
}
目录结构
|- mock
|- node_modules
|- package.json
|- public
|- src
|- asserts
|- components
|- models
|- routes
|- services
|- utils
|- router.js
|- index.js
|- index.css
|- .editorconfig
|- .eslintrc
|- .gitignore
|- .roadhogrc.mock.js
|- .webpackrc
- mock 存放用于 mock 数据的文件;
- public 一般用于存放静态文件,打包时会被直接复制到输出目录(./dist);
- src 文件夹用于存放项目源代码;
- asserts 用于存放静态资源,打包时会经过 webpack 处理;
- components 用于存放 React 组件,一般是该项目公用的无状态组件;
- models 用于存放模型文件
- routes 用于存放需要 connect model 的路由组件;
- services 用于存放服务文件,一般是网络请求等;
- utils 工具类库
- router.js 路由文件
- index.js 项目的入口文件
- index.css 一般是共用的样式
- .editorconfig 编辑器配置文件
- .eslintrc ESLint配置文件
- .gitignore Git忽略文件
- .roadhogrc.mock.js Mock配置文件
- webpackrc 自定义的webpack配置文件,JSON格式,如果需要 JS 格式,可修改为 .webpackrc.js
和vue-cli生成的目录结构类似,其中有几个不同点强调一下
- model文件类似于vuex中store 用来控制状态
- routes文件夹相当于常见的pages,当然,这里也可以改成pages
- 多了mock文件夹,如果只进行本地开发,可以用过.roadhogrc.mock.js配置数据进行本地开发
- dva开发组件都是return形式,不像vue是单写html,这两个区别比较大,需要熟悉
5.开始开发
我们通过vue对比进行开发,所以从我们熟悉的开始(routes已换成pages)
// .webpackrc.js
import { resolve } from 'path'
let publicPath
module.exports = {
publicPath,
outputPath: './dist',
// 按需加载antd组件
extraBabelPlugins: [
[
'import',
{
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
},
],
],
alias: {
components: resolve(__dirname, './src/components'),
utils: resolve(__dirname, './src/utils'),
pages: resolve(__dirname, './src/pages'),
services: resolve(__dirname, './src/services'),
api: resolve(__dirname, './src/api'),
models: resolve(__dirname, './src/models'),
img: resolve(__dirname, './src/assets/img'),
},
}
1. 新增文件夹src/api将接口地址写在里面
|- mock
|- node_modules
|- package.json
|- public
|- src
+ |- api
|- asserts
|- components
|- models
|- pages
|- services
|- utils
|- router.js
|- index.js
|- index.css
|- .editorconfig
|- .eslintrc
|- .gitignore
|- .roadhogrc.mock.js
|- .webpackrc
例如:
// src/api/commodity.js
// 商品列表
export const commodityListUrl = url
2. services 将所有后台的接口请求都写在里面,例如
// src/services/commodityService.js
import request from 'utils/request'
import {
commodityListUrl,
} from 'api/commodity'
import { createAFordownLoad } from 'utils/methods'
/**
* 获取商品列表
*
* @param {*} payload
* @returns
*/
export const getCommodityList = async payload => {
try {
const { result } = await request({
url: `${commodityListUrl}`,
data: payload,
})
return Promise.resolve(result)
} catch (e) {
return Promise.resolve(e.message)
}
}
3. 添加路由
// src/router.js 注意这里是示意代码 不是业务代码,在实际开发中会根据情况变化
import React from 'react';
import { Router, Route } from 'dva/router';
import commodity from './pages/list';
// 假装有其他
import example from './pages/example';
function RouterConfig({ history }) {
return (
<Router history={history}>
<Route path="/" exact component={commodity} />
<Route path="/example" exact component={example} />
</Router>
);
}
export default RouterConfig;
dva 内置了 dynamic 方法用于实现组件的动态加载,用法如下:
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
})
实际使用时,可以对其进行简单的封装,否则每个路由组件都这么写一遍很麻烦。
例如我们可以:
import routes from '' // 把Route集中写进一个文件
function RouterConfig({ history }) {
return (
<Router history={history}>
<Switch>
{routes.map(({ path, ...dynamics }, key) => (
<Route
key={key}
path={path}
exact
component={dynamic({ app, ...dynamics })}
/>
))}
</Switch>
</Router>
);
}
4. modal设计
Model 是 dva 最重要的部分,可以理解为 redux、react-redux、redux-saga 的封装。
通常项目中一个模块对应一个 model,一个基本的 model 如下:
import { hashHistory } from 'dva/router'
import { query } from '../services/users'
export default {
namespace: 'commodity',
state: {
list: [],
},
subscriptions: {
setup({ dispatch, history }) {
// 进入页面就调用
history.listen(location => {
if (location.pathname === '/') {
dispatch({
type: 'query',
payload: {},
})
}
})
},
},
effects: {
*query({ payload }, { select, call, put }) {
yield put({ type: 'showLoading' })
const { data } = yield call(query)
if (data) {
yield put({
type: 'querySuccess',
payload: {
list: data.data,
total: data.page.total,
current: data.page.current,
},
})
}
},
*create() {},
// delete为关键字
*'delete'() {},
*update() {},
},
reducers: {
showLoading(state, action) {
return {
...state,
loading: true,
}
},
showModal() {},
hideModal() {},
querySuccess(state, action) {
return {
...state,
...action.payload,
loading: false,
}
},
createSuccess() {},
deleteSuccess() {},
updateSuccess() {},
},
}
- namespace 是该 model 的命名空间,同时也是全局 state 上的一个属性,只能是字符串,不支持使用 . 创建多层命名空间。
- state 是状态的初始值。
- reducer 是唯一可以修改 state 的地方,由 action 触发,它有 state 和 action 两个参数。 类似vue中store的mutation
- effects 用于处理异步操作,不能直接修改 state,由 action 触发,也可触发 action。它只能是 generator 函数,并且有 action 和 effects 两个参数。第二个参数 effects 包含 put、call 和 select 三个字段,put 用于触发 action,call 用于调用异步处理逻辑,select 用于从 state 中获取数据。 类似vue中store中的action
- subscriptions 用于订阅某些数据源,并根据情况 dispatch 某些 action,格式为 ({ dispatch, history }, done) => unlistenFunction。
5. 组件设计(由于后台项目比较大,建议写在对应的页面component里)
6. 连通组件和modal数据
|- src
|- api
|- asserts
|- components
|- models
|- pages
+ |- commodity
+ |- component
+ |- dataTable.js
+ |- list.js
// src/pages/commodity/component/dataTable.js
import React, { Component } from 'react'
import { Table, Button } from 'antd' // antd 组件
class DataTable extends Component {
render() {
const {
data: { list, pagination },
} = this.props // 获取数据
const columns = [] // 定义表头及内容
return (
<Table
rowKey="draftSn"
columns={columns}
DataTable
dataSource={list}
pagination={pagination}
/>
)
}
}
export default DataTable
// src/pages/commodity/list.js
import React, { Component } from 'react'
import { connect } from 'dva'
import DataTable from './component/dataTable' // 注意组件一定要大写
class CommodityList extends Component {
state = {
list: [],
}
render() {
// 从modal获取数据
const {
commodity, // modal数据
dispatch, // service方法
location, // 可以获取url相关信息
history, // 路由
} = this.props
// 为组将传参
const tableProps = {
data: commodity,
dispatch,
history,
}
return (
<div>
<DataTable {...tableProps} />
</div>
)
}
}
// 连通modal和页面数据
function mapStateToProps({ commodity }) {
return { commodity }
}
export default connect(mapStateToProps)(CommodityList)
网友评论