Cap1 前言
对React温习差不多了,是时候尝试制作个小项目。目前使用UmiJS + DvaJS来构建项目。
UmiJS是一个可插拔的企业级 react 应用框架,具体可查看官网文档
DvaJS是dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。官网地址:https://dvajs.com
Cap2 创建项目
创建UmiJS项目很简单,根据文档的通过脚手架创建项目步骤就行了,这里不多描述。
提示安装类型:
Select the boilerplate type 选择 app
Do you want to use typescript? 选择 No
What functionality do you want to enable? 空格键选择 antd
和dva
回车确定
antd
是蚂蚁金服的React UI框架,它的设计非常精美,其官网地址:https://ant.design/index-cn。这里加个UI框架只是为了项目数据展示美观些。
Cap3 配置代理
下面插点数据,既然是用户管理系统就需要一些用户信息,利用mock制造一些json数据:http://jsonplaceholder.typicode.com/users
,由于跨域等问题,还是将这个地址反代自己地址吧。根据UmiJS
问题的Proxy配置,需要在项目目录下的.umirc.js
或者config/config.js
添加编辑:
"proxy": {
"/api": {
"target": "http://jsonplaceholder.typicode.com/",
"changeOrigin": true,
"pathRewrite": { "^/api" : "" }
}
}
然后启动项目yarn start
访问地址:http://localhost:8000/api/users
是否成功。
Cap4 生成路由
编写路由前有必要先了解下UmiJS
路由运行方式:UmiJS 路由,路由就是应用不同的页面组成的,如果地址栏是/user/
则页面应该是src/pages/user.js
。当然你可以手动新建个js/css
文件,不过UmiJS
提供npx
新建路由的方式:
yarn global add npx # 安装npx模块
npx umi g page user # 新建/user路由
然后可以在http://localhost:8000/user
访问了。
Cap5 构造 model 和 services
如果在src/model
和src/services
目录下新建文件名字和src/pages
相同,Umi会自动注入同名业务代码。
可以利用DvaJS
中的fetch
方式访问接口获取数据,然后在src/utils/request.js
编写一层fetch
封装方法:
import fetch from 'dva/fetch';
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default async function request(url, options) {
const response = await fetch(url, options);
checkStatus(response);
const data = await response.json();
const ret = {
data,
headers: {},
};
if (response.headers.get('x-total-count')) {
ret.headers['x-total-count'] = response.headers.get('x-total-count');
}
return ret;
}
关于具体的Fetch
资料可以到网上查阅。
然后建立请求接口的文件,在新建src/services/user.js
:
import { PAGE_SIZE } from '../constants'
import request from '@/utils/request'
export function queryUserList({ page = 1 }) {
return request(`/api/users?_page=${page}&_limit=${PAGE_SIZE}`);
}
这里的请求是_page
是当前页码,_limit
是当前页码数据数量。
在src/constants.js
编写:
export const PAGE_SIZE = 5;
当然可以在请求参数里写死。
请求服务业务写完了,下面写models
层业务,该业务主要是处理数据和逻辑,DvaJS
通过model
的概念把一个领域的模型管理起来,包含同步更新state
的reducers
,处理异步逻辑的effects
,订阅数据源的 subscriptions
:
新建src/models/users.js
import { queryUserList } from '@/services/users';
export default {
namespace: 'users',
state: {
list: [],
total: null,
page: null,
},
reducers: {
// 保存数据
save(state, { payload: { data: list, total, page } }) {
// 复制数组,将list, total, page内容复制到state
return { ...state, list, total, page };
},
},
effects: {
// 访问接口获取数据 并且保存数据
*fetchUserList({ payload: { page = 1 } }, { call, put }) {
const { data, headers } = yield call(queryUserList, { page });
yield put({
type: 'save',
payload: {
data,
total: parseInt(headers['x-total-count'], 10) ,
page: parseInt(page, 10)
}
});
},
},
subscriptions: {
// https://github.com/dvajs/dva/issues/174
setup({ dispatch, history }) {
return history.listen(({ pathname, query }) => {
// 进入'/users'地址触发fetchUserList
if (pathname === '/users') {
dispatch({ type: 'fetchUserList', payload: query });
}
});
},
},
};
看着有点多,但里面内容基本都是固定规范,详细的说明可以去DvaJS
官网文档 Model查阅:
namespace
:表示在全局state
上的唯一名称state
:表示初始值reducers
:用于处理同步操作,唯一可以修改state
的地方effects
:以key/value
格式定义effect
。用于处理异步操作和业务逻辑,不直接修改state
subscriptions
:用于订阅一个数据源,然后根据需要 dispatch 相应的 action
Cap6构建 UI
修改src/pages/users.js
:
import { connect } from 'dva';
import { Table, Pagination, Popconfirm } from 'antd';
import styles from './users.css';
import { PAGE_SIZE } from '../constants';
/**
* 用户列表管理
* @param dispatch DvaJS dispatch函数 用于触发action 的函数 详解:https://dvajs.com/guide/concepts.html#dispatch-%E5%87%BD%E6%95%B0
* @param dataSource 用户列表信息
* @param total 总数据数量
* @param loading 加载状态
* @param current 当前页码
* @returns {*}
* @constructor
*/
function Users({ dispatch, list: dataSource, loading, total, page: current }) {
function deleteHandler(id) {
console.warn(`TODO: ${id}`);
}
/**
* 页码改变的回调
* @param page 改变后的页码
* @param pageSize 每页条数
*/
function onChangeUserPagination(page, pageSize) {
dispatch({
type: 'users/fetchUserList',
payload: {
page: page
}, // 需要传递的信息
});
}
const columns = [
{
title: '用户名',
dataIndex: 'name',
key: 'name',
},
{
title: '电子邮件',
dataIndex: 'email',
key: 'email',
},
{
title: '个人网站',
dataIndex: 'website',
key: 'website',
},
{
title: '操作',
key: 'operation',
render: (text, { id }) => (
<span className={styles.operation}>
<Button>编辑</Button>
<Popconfirm title="Confirm to delete?" onConfirm={deleteHandler.bind(null, id)}>
<Button>删除</Button>
</Popconfirm>
</span>
),
},
];
return (
<div className={styles.normal}>
<div>
<Table
loading={loading}
columns={columns}
dataSource={dataSource}
rowKey={record => record.id}
pagination={false}
/>
<Pagination
className="ant-table-pagination"
total={total} // 数据总数
current={current} // 当前页数
pageSize={PAGE_SIZE} // 每页条数
onChange={onChangeUserPagination}
/>
</div>
</div>
);
}
function mapStateToProps(state) {
const { list, total, page } = state.users;
return {
list,
total,
page,
loading: state.loading.models.users,
};
}
export default connect(mapStateToProps)(Users);
这个页面用到了antd
的表格和分页俩个组件,渲染组件是标准的React
函数组件,接受带数据的props
对象。
上面是进入页面获取数据,如果是主动性获取数据也很简单:先注释src/moudel/users.js
中的subscriptions
订阅模式模块,然后在src/pages/users.js
添加按钮:
function getUserListData() {
dispatch({
type: 'users/fetchUserList',
payload: {}, // 需要传递的信息
});
}
...
return (
<div className={styles.normal}>
<div>
<Button type="primary" onClick={getUserListData}>获取用户数据</Button>
<Table
loading={loading}
columns={columns}
dataSource={dataSource}
rowKey={record => record.id}
pagination={false}
/>
...
就这样一个简单的页面构建完成。所以完整的功能页面就不在这篇文章详细的编写了,整个业务逻辑和上述过程差不多。
虽然之前做过不少Vue.js
单应用页面开发,对比React
应用开发,虽然是吃力了些,也麻烦了些,但很对地方写法比Vue.js
开发优雅了许多。