最近新项目用的umi框架,看完官网api后,打算运用umi+antd pro+dva上手实现个简单的todoList练习一下
在网上找到了一个效果示例
搭建项目:
前置条件:需要安装node环境及node版本10.13以上
查看node版本:
node -v
先找个地方建个空目录
mkdir myProject-umi&& cd myProject-umi
通过官方工具创建项目
npx @umijs/create-umi-app
项目创建成功,目录结构如下
package.json
{
"private": true,
"scripts": {
"start": "umi dev",
"build": "umi build",
"postinstall": "umi generate tmp",
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
"test": "umi-test",
"test:coverage": "umi-test --coverage"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,less,md,json}": [
"prettier --write"
],
"*.ts?(x)": [
"prettier --parser=typescript --write"
]
},
"dependencies": {
"@ant-design/pro-layout": "^6.5.0",
"react": "17.x",
"react-dom": "17.x",
"umi": "^3.5.20"
},
"devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.5.20",
"lint-staged": "^10.0.7",
"prettier": "^2.2.0",
"typescript": "^4.1.2",
"yorkie": "^2.0.0"
}
}
这里umi是3.x版本,umi3.x官方文档介绍 (umijs.org),默认的脚手架内置了 @umijs/preset-react,包含布局、权限、国际化、dva、简易数据流等常用功能。比如想要 ant-design-pro 的布局,编辑 .umirc.ts 配置 layout: {},并且需要安装 @ant-design/pro-layout。
默认生成的.umirc.ts配置文件
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: [
{ path: '/', component: '@/pages/index' },
],
fastRefresh: {},
});
安装依赖:npm install
启动项目:npm start
项目启动后会自动在src下 生成临时文件.umi文件夹,不要提交 .umi 目录到 git 仓库(一般在生成项目时间gitignore文件已经加上了.umi),他们会在 umi dev 和 umi build 时被删除并重新生成。
在浏览器中输入地址打开网页,可以看到默认的page页面
如果想要antd pro的布局,可以在.umirc.ts中配置layout:{},或新建layout文件夹下的自定义布局
考虑到实现一个todoList 需要状态管理 国际化等,目录结构改造如下
├── package.json
├── .umirc.ts //umi 配置,同 config/config.js,二选一,umi内置功能的配置和插件的配置,由于项目比较简单这里选用.umirc.ts
├── mock //mock数据
└── todoList.ts
└── src
├── .umi //自动生成的临时文件
├── components //公共组件
└── TodoList.tsx
├── pages // 所有路由组件在这里
└── todoList //todoList页面
├── index.less
└── index.tsx
├── model //状态管理
└── model.ts
├── locales //国际化
├── en-US.ts
└── zn-CN.ts
├── service //请求服务
└── index.ts
配置
.umirc.ts文件需要配置的项
- 页面路由:配置routes,格式为路由信息的数组。
{
name: 'dashboard',
icon: 'dashboard',
hideInMenu: true,
hideChildrenInMenu: true,
hideInBreadcrumb: true,
authority: ['admin'],
}
name
: 当前路由在菜单和面包屑中的名称,注意这里是国际化配置的key
,具体展示菜单名可以在/src/locales/zh-CN.js
进行配置。icon
: 当前路由在菜单下的图标名。hideInMenu
: 当前路由在菜单中不展现,默认false
。hideChildrenInMenu
: 当前路由的子级在菜单中不展现,默认false
。hideInBreadcrumb
: 当前路由在面包屑中不展现,默认false
。authority
: 允许展示的权限,不设则都可见,详见:权限管理路由用按需引入的方式,配置dynamicImport项
热更新:fastRefresh
国际化配置:locale
default默认语言
antd是否支持国际化layout:antd pro布局
// .umirc.ts
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
layout:{}, //antdpro布局
routes: [
{
name: 'todo列表', //菜单名称
path: '/',
component:'./todoList'
}
], //路由
fastRefresh: {}, //热更新
locale: {
default: 'zh-CN',
antd: true,
}, //国际化
dynamicImport: {
} //动态引入
});
mock
todoList主要的操作就是增删改查,
所以这里加了两个接口,查询list的接口,和更新list数据的接口(增删改都调用这个接口),并用setTimeout来模拟异步请求
- getTodoList:请求初始todolist数据
- updateTodoList:更新todolist数据
// mock/mock.ts
import { Response, Request } from "umi";
export default {
'GET /api/getTodoList': (req: Request, res: Response) => {
setTimeout(() => {
res.send({
status: 'ok',
code: '200',
data: [
{content: '初始任务1', status: '0', key: '初始任务1'},
{content: '初始任务2', status: '1', key: '初始任务2'}
]
})
}, 2000)
},
'POST /api/updateTodoList': (req: Request, res: Response) => {
setTimeout(() => {
res.send({
status: 'ok',
code: '200',
})
}, 2000)
}
}
locales
//locales/en-US.ts
export default {
'placeholder':'what to do',
'addTask': 'add task',
'delete': 'delete',
'allTask': 'all task',
'completedTask': 'completed task',
'uncompletedTask': 'uncompleted task'
};
//locales/zh-CN.ts
export default {
'placeholder':"你想做点什么",
'addTask': '添加任务',
'delete': '删除',
'allTask': '所有任务',
'completedTask': '已完成任务',
'uncompletedTask': '待办任务'
};
model
model 作状态管理,包含
- namespace: 表示在全局 state 上的 key
- state:状态数据
- reducers :管理同步方法,必须是纯函数
- effects :管理异步操作,采用了 generator 的相关概念
- subscriptions:订阅数据源
在 umi 中会按照约定的目录来注册 model,且文件名会被识别为 model 的 namespace
model 还分为 src/models/.js 目录下的全局 model,和 src/pages/**/models/.js 下的页面 model
这里model文件建在src目录下,作为全局的model
//models/models.ts
import type {Reducer, Effect} from 'umi';
import {getTodoList, updateTodoList} from '@/services/index'
export interface IList {
content: string;
status: string;
key: string;
}
export type ModelState = {
list: IList[];
}
export interface PropsFromDva {
data: ModelState;
}
export type ModelType = {
namespace: string;
state: ModelState;
reducers: {
changeTaskList: Reducer;
};
effects: {
updateTodoList: Effect,
getTodoList: Effect
}
}
const Model: ModelType = {
namespace: 'data',
state: {
list: []
},
reducers: {
changeTaskList(state, {payload}) {
return {
...state,
list: payload
}
}
},
effects: {
*getTodoList(_,{call, put}) {
const response = yield call(getTodoList);
yield put({
type: 'changeTaskList',
payload: response.data
})
},
*updateTodoList({payload},{call, put}){
const response = yield call(updateTodoList,payload)
return response;
}
}
}
export default Model;
service
//services/index.ts
import {request} from "umi";
export function updateTodoList(params:any) {
return request(`/api/updateTodoList`, {
method: 'post',
params
})
}
export function getTodoList() {
return request(`/api/getTodoList`, {
method: 'get',
})
}
page
为了在做增删改操作时,三个tab同步更新数据,一个方法就是将数据存储在model,页面从state中拿到最新数据
我们知道可以运用两种方式关联state和view
1、connect:需要用 dva 或 umi 中导出 connect 方法,然后将 model 绑定到组件,mapStateToProps,在props中拿到state
2、dva 2.6x 之后,提供的hook: useSelector、useDispatch
这里使用hook的形式,useDispatch useSelector
// pages/todoList/index.tsx
import styles from './index.less';
import TaskList from '@/components/TodoList';
import { useSelector, useDispatch } from 'dva';
import { PropsFromDva } from '@/models/model';
import { Tabs } from 'antd';
import { useEffect } from 'react';
import { useIntl } from 'umi';
const { TabPane } = Tabs;
const TodoList = () => {
const dispatch = useDispatch();
const intl = useIntl();
const { list } = useSelector((state:PropsFromDva) => state.data);
const completedList = list.filter(item => item.status === '1');
const uncompletedList = list.filter(item => item.status === '0');
const selectedKey = completedList.map(item => item.key);
const tabList = [
{tab: intl.formatMessage({id: 'allTask'}), key: '1', list: list},
{tab: intl.formatMessage({id: 'completedTask'}), key: '2', list: uncompletedList},
{tab: intl.formatMessage({id: 'uncompletedTask'}), key: '3', list: completedList},
];
useEffect(() => {
dispatch({type: 'data/getTodoList'});
}, [])
return (
{tabList.map(item =>
)}
);
};
export default TodoList;
component
list组件 包含增删改操作
// components/TodoList/index.tsx
import styles from './index.less';
import { useState } from 'react';
import { Button, Checkbox, Input } from 'antd';
import { useDispatch } from 'dva';
import { IList } from '@/models/model';
import { useIntl } from 'umi';
const CheckboxGroup = Checkbox.Group;
interface IProps {
list: IList[];
selectedKey: string[];
}
interface IResponse {
status: string;
code: string;
}
const TaskList = (props: IProps) => {
const dispatch = useDispatch();
const intl = useIntl();
const {list, selectedKey} = props;
const [taskContent, setTaskContent] = useState('');
const updateTodoList= async (list: IList[]) => {
const res: IResponse = await dispatch({
type: 'data/updateTodoList',
payload: list
})
}
const deleteItem = (key: string) => {
const newList = list.filter((item: IList) => item.key!==key);
updateTodoList(newList);
dispatch({
type: 'data/changeTaskList',
payload: newList
});
};
const onChangeStatus = (checkedValues:any) => {
const newList:any[] = [];
list.forEach((item: IList) => {
if(checkedValues.includes(item.key)) {
newList.push({...item, status: '1'});
}
else{
newList.push({...item, status: '0'});
}
})
updateTodoList(newList);
dispatch({
type: 'data/changeTaskList',
payload: newList
});
};
const addItem = () => {
const newList = [...list, {content: taskContent, status: '0', key: taskContent}];
updateTodoList(newList);
dispatch({
type: 'data/changeTaskList',
payload: newList
});
setTaskContent('');
};
return (
<>
{list?.map((item: IList) => (
{item.content}
))}
setTaskContent(e.target.value)}
/>
>
);
};
export default TaskList;
最终效果如图