乐趣区

React框架Umi实战(2)整合dva开发后台管理系统

umi 官方推荐结合 dva 使用更配哦,其实他们都是同一位开发者开发的,属于阿里内部开源框架。
1 修改.umirc.js,开启 dva 支持

// ref: https://umijs.org/config/
export default {
plugins: [
// ref: https://umijs.org/plugin/umi-plugin-react.html
[‘umi-plugin-react’, {
antd: true,
dva: true,
dva: {
immer: true
},
dynamicImport: false,
title: ‘umis’,
dll: false,
hardSource: false,
routes: {
exclude: [
/components\//,
],
},
}],
],
}
2 修改 layouts

先搞一个后台的布局, 可以参考 ANT DESIGN PRO https://pro.ant.design/layouts\index.js
import {connect} from ‘dva’;
import {Table, Pagination, Popconfirm} from ‘antd’;
import styles from ‘./users.css’;
const PAGE_SIZE = 5;

function Users({list: dataSource, total, page: current}) {
function deleteHandler(id) {
console.warn(`TODO: ${id}`);
}

const columns = [
{
title: ‘Name’,
dataIndex: ‘name’,
key: ‘name’,
render: text => <a href=””>{text}</a>,
},
{
title: ‘Email’,
dataIndex: ’email’,
key: ’email’,
},
{
title: ‘Website’,
dataIndex: ‘website’,
key: ‘website’,
},
{
title: ‘Operation’,
key: ‘operation’,
render: (text, { id}) => (
<span className={styles.operation}>
<a href=””>Edit</a>
<Popconfirm title=”Confirm to delete?” onConfirm={deleteHandler.bind(null, id)}>
<a href=””>Delete</a>
</Popconfirm>
</span>
),
},
];

return (
<div className={styles.normal}>
<div>
<Table
columns={columns}
dataSource={dataSource}
rowKey={record => record.id}
pagination={false}
/>
<Pagination
className=”ant-table-pagination”
total={total}
current={current}
pageSize={PAGE_SIZE}
/>
</div>
</div>
);
}

function mapStateToProps(state) {
const {list, total, page} = state.users;
console.log(state.test);
return {
list,
total,
page,
};
}

export default connect(mapStateToProps)(Users);

layouts\index.less
.logo {
height: 64px;
position: relative;
line-height: 64px;
transition: all 0.3s;
background: #002140;
overflow: hidden;
img {
display: inline-block;
vertical-align: middle;
height: 32px;
}
h1 {
color: white;
display: inline-block;
vertical-align: middle;
font-size: 20px;
margin: 0 0 0 12px;
font-family: ‘Myriad Pro’, ‘Helvetica Neue’, Arial, Helvetica, sans-serif;
font-weight: 600;
}
}

.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color .3s;
.logo {
height: 32px;
background: rgba(255,255,255,.2);
margin: 16px;
}
}

.sider {
min-height: 100vh;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
position: relative;
z-index: 10;
&.ligth {
background-color: white;
.logo {
background: white;
h1 {
color: #002140;
}
}
}
}
浏览器访问你会看到如下效果:
3 改造 users 页面,完成 dva 整个流程。

在 pages 下创建 users 目录
创建 index.js 会生成默认路由 http://localhost:8000/users

import {connect} from ‘dva’;
import {Table, Pagination, Popconfirm} from ‘antd’;
import styles from ‘./users.css’;
const PAGE_SIZE = 5;

function Users({list: dataSource, total, page: current}) {
function deleteHandler(id) {
console.warn(`TODO: ${id}`);
}

const columns = [
{
title: ‘Name’,
dataIndex: ‘name’,
key: ‘name’,
render: text => <a href=””>{text}</a>,
},
{
title: ‘Email’,
dataIndex: ’email’,
key: ’email’,
},
{
title: ‘Website’,
dataIndex: ‘website’,
key: ‘website’,
},
{
title: ‘Operation’,
key: ‘operation’,
render: (text, { id}) => (
<span className={styles.operation}>
<a href=””>Edit</a>
<Popconfirm title=”Confirm to delete?” onConfirm={deleteHandler.bind(null, id)}>
<a href=””>Delete</a>
</Popconfirm>
</span>
),
},
];

return (
<div className={styles.normal}>
<div>
<Table
columns={columns}
dataSource={dataSource}
rowKey={record => record.id}
pagination={false}
/>
<Pagination
className=”ant-table-pagination”
total={total}
current={current}
pageSize={PAGE_SIZE}
/>
</div>
</div>
);
}

function mapStateToProps(state) {
const {list, total, page} = state.users;
console.log(state.test);
return {
list,
total,
page,
};
}

export default connect(mapStateToProps)(Users);
创建对应的 model.js, 因为只有一个 model 所以不需要创建 models 目录。但是名字必须为 model.js,不然不能自动注册。
// 只有一个 model 的话,可以不用建 models 目录。但名字必须为 model。js
import * as usersService from ‘./service’;
export default {
namespace: ‘users’,
state: {
list: [],
total: null,
},
reducers: {
save(state, { payload: { data: list, total} }) {
return {…state, list, total};
},
},
effects: {
*fetch({payload: { page} }, {call, put}) {
const {data} = yield call(usersService.fetch, { page});
yield put({type: ‘save’, payload: { data, total: data.length} });
},
},
subscriptions: {
setup({dispatch, history}) {
return history.listen(({pathname, query}) => {
if (pathname === ‘/users’) {
dispatch({type: ‘fetch’, payload: query});
}
});
},
},
};
创建 service.js 这个不需要约定名称,你随便起名字
import request from ‘../../utils/request’;

export function fetch({page = 1}) {
return request(`/api/users?_page=${page}&_limit=5`);
}
创建 utils 目录,封装统一请求 api utils\request.js

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
};
return ret;
}
创建 mock 假数据 mock\user.js

export default {
‘/api/users’: [
{
“id”: 1,
“name”: “Leanne Graham”,
“username”: “Bret”,
“email”: “Sincere@april.biz”,
“address”: {
“street”: “Kulas Light”,
“suite”: “Apt. 556”,
“city”: “Gwenborough”,
“zipcode”: “92998-3874”,
“geo”: {
“lat”: “-37.3159”,
“lng”: “81.1496”
}
},
“phone”: “1-770-736-8031 x56442”,
“website”: “hildegard.org”,
“company”: {
“name”: “Romaguera-Crona”,
“catchPhrase”: “Multi-layered client-server neural-net”,
“bs”: “harness real-time e-markets”
}
},
{
“id”: 2,
“name”: “Ervin Howell”,
“username”: “Antonette”,
“email”: “Shanna@melissa.tv”,
“address”: {
“street”: “Victor Plains”,
“suite”: “Suite 879”,
“city”: “Wisokyburgh”,
“zipcode”: “90566-7771”,
“geo”: {
“lat”: “-43.9509”,
“lng”: “-34.4618”
}
},
“phone”: “010-692-6593 x09125”,
“website”: “anastasia.net”,
“company”: {
“name”: “Deckow-Crist”,
“catchPhrase”: “Proactive didactic contingency”,
“bs”: “synergize scalable supply-chains”
}
},
{
“id”: 3,
“name”: “Clementine Bauch”,
“username”: “Samantha”,
“email”: “Nathan@yesenia.net”,
“address”: {
“street”: “Douglas Extension”,
“suite”: “Suite 847”,
“city”: “McKenziehaven”,
“zipcode”: “59590-4157”,
“geo”: {
“lat”: “-68.6102”,
“lng”: “-47.0653”
}
},
“phone”: “1-463-123-4447”,
“website”: “ramiro.info”,
“company”: {
“name”: “Romaguera-Jacobson”,
“catchPhrase”: “Face to face bifurcated interface”,
“bs”: “e-enable strategic applications”
}
},
{
“id”: 4,
“name”: “Patricia Lebsack”,
“username”: “Karianne”,
“email”: “Julianne.OConner@kory.org”,
“address”: {
“street”: “Hoeger Mall”,
“suite”: “Apt. 692”,
“city”: “South Elvis”,
“zipcode”: “53919-4257”,
“geo”: {
“lat”: “29.4572”,
“lng”: “-164.2990”
}
},
“phone”: “493-170-9623 x156”,
“website”: “kale.biz”,
“company”: {
“name”: “Robel-Corkery”,
“catchPhrase”: “Multi-tiered zero tolerance productivity”,
“bs”: “transition cutting-edge web services”
}
},
{
“id”: 5,
“name”: “Chelsey Dietrich”,
“username”: “Kamren”,
“email”: “Lucio_Hettinger@annie.ca”,
“address”: {
“street”: “Skiles Walks”,
“suite”: “Suite 351”,
“city”: “Roscoeview”,
“zipcode”: “33263”,
“geo”: {
“lat”: “-31.8129”,
“lng”: “62.5342”
}
},
“phone”: “(254)954-1289”,
“website”: “demarco.info”,
“company”: {
“name”: “Keebler LLC”,
“catchPhrase”: “User-centric fault-tolerant solution”,
“bs”: “revolutionize end-to-end systems”
}
},
{
“id”: 6,
“name”: “Mrs. Dennis Schulist”,
“username”: “Leopoldo_Corkery”,
“email”: “Karley_Dach@jasper.info”,
“address”: {
“street”: “Norberto Crossing”,
“suite”: “Apt. 950”,
“city”: “South Christy”,
“zipcode”: “23505-1337”,
“geo”: {
“lat”: “-71.4197”,
“lng”: “71.7478”
}
},
“phone”: “1-477-935-8478 x6430”,
“website”: “ola.org”,
“company”: {
“name”: “Considine-Lockman”,
“catchPhrase”: “Synchronised bottom-line interface”,
“bs”: “e-enable innovative applications”
}
},
{
“id”: 7,
“name”: “Kurtis Weissnat”,
“username”: “Elwyn.Skiles”,
“email”: “Telly.Hoeger@billy.biz”,
“address”: {
“street”: “Rex Trail”,
“suite”: “Suite 280”,
“city”: “Howemouth”,
“zipcode”: “58804-1099”,
“geo”: {
“lat”: “24.8918”,
“lng”: “21.8984”
}
},
“phone”: “210.067.6132”,
“website”: “elvis.io”,
“company”: {
“name”: “Johns Group”,
“catchPhrase”: “Configurable multimedia task-force”,
“bs”: “generate enterprise e-tailers”
}
},
{
“id”: 8,
“name”: “Nicholas Runolfsdottir V”,
“username”: “Maxime_Nienow”,
“email”: “Sherwood@rosamond.me”,
“address”: {
“street”: “Ellsworth Summit”,
“suite”: “Suite 729”,
“city”: “Aliyaview”,
“zipcode”: “45169”,
“geo”: {
“lat”: “-14.3990”,
“lng”: “-120.7677”
}
},
“phone”: “586.493.6943 x140”,
“website”: “jacynthe.com”,
“company”: {
“name”: “Abernathy Group”,
“catchPhrase”: “Implemented secondary concept”,
“bs”: “e-enable extensible e-tailers”
}
},
{
“id”: 9,
“name”: “Glenna Reichert”,
“username”: “Delphine”,
“email”: “Chaim_McDermott@dana.io”,
“address”: {
“street”: “Dayna Park”,
“suite”: “Suite 449”,
“city”: “Bartholomebury”,
“zipcode”: “76495-3109”,
“geo”: {
“lat”: “24.6463”,
“lng”: “-168.8889”
}
},
“phone”: “(775)976-6794 x41206”,
“website”: “conrad.com”,
“company”: {
“name”: “Yost and Sons”,
“catchPhrase”: “Switchable contextually-based project”,
“bs”: “aggregate real-time technologies”
}
},
{
“id”: 10,
“name”: “Clementina DuBuque”,
“username”: “Moriah.Stanton”,
“email”: “Rey.Padberg@karina.biz”,
“address”: {
“street”: “Kattie Turnpike”,
“suite”: “Suite 198”,
“city”: “Lebsackbury”,
“zipcode”: “31428-2261”,
“geo”: {
“lat”: “-38.2386”,
“lng”: “57.2232”
}
},
“phone”: “024-648-3804”,
“website”: “ambrose.net”,
“company”: {
“name”: “Hoeger LLC”,
“catchPhrase”: “Centralized empowering task-force”,
“bs”: “target end-to-end models”
}
}
],
}
访问 http://localhost:8000/users
4 总结

按 umi 推荐的方式去创建目录
注意约定名 pages 目录下的所有 js 都回被默认生成路由,可通过配置排出
省略了 model 的手动注册,至于忽略 namespace,我试了不行
各个 page 的 model 不能相互引用?不是很明白什么意思,我试了每个 page 都能获取所有的 state,并不是只能获取属于自己的。可能是我理解有误。
没有看出来 umi 的好处,只是目录的区别吗?

这个 issues 下有关于 umi 按 page 分的讨论。https://github.com/sorrycc/bl… 希望知道 umi 核心优势的小伙伴留言解释下,前端菜鸟不是很懂。
别忘了关注我 mike 啥都想搞
还有其他后端技术分享在我的公众号。

退出移动版