React umi

React umi

一月 25, 2021

umi

  1. 安装
1
2
3
4
5
6
7
$ npm i yarn tyarn -g
# 后面文档里的 yarn 换成 tyarn
$ tyarn -v

$ yarn create @umijs/umi-app

$ yarn
  1. 介绍目录和文件
    • mock
      • 模拟数据文件【 express 】
    • node_modules
      • 依赖包
    • src 源代码开发目录
      • .umi 临时文件,不要做任何操作
      • pages 页面
        • index.less index 组件的样式文件
        • index.tsx index 组件
      • .editorConfig 编辑器配置文件
      • .gitignore git 上传代码托管平台忽略配置文件
      • .prettierignore 格式化忽略配置文件
      • .prettierrc 格式化配置文件
      • .umirc.ts 项目配置文件
      • package.json 项目记录文件
      • README.md 项目说明书,
      • tsconfig.json ts 配置文件
      • typing.d.ts 类型声明文件
      • yarn.lock 锁定项目中插件的版本

配置 layout 插件

https://umijs.org/zh-CN/plugins/plugin-layout

  1. 创建 src/app.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { useEffect } from "react";
import Tab from "@/components/Tab";
import MyFoot from "@/components/MyFoot";
import { history } from "umi";
import * as cookie from "@/utils/cookie";
export const layout = () => {
return {
rightContentRender: () => <Tab />,
footerRender: () => <MyFoot />,
onPageChange: () => {
// console.log('页面改变了');
const { location } = history;
const token = cookie.getCookie("token");
if (!token && location.pathname !== "/login") {
// 判断不是登陆页面和有没有token
history.push("/login");
}
},
};
};
  1. .umirc.ts
    1
    2
    3
    4
    5
    6
    7
    layout: {
    // 支持任何不需要 dom 的
    // https://procomponents.ant.design/components/layout#prolayout
    name: 'Ant Design', // 用来修改名字
    locale: true, // 国际化
    layout: 'side',
    },

mock 模拟数据

https://umijs.org/zh-CN/docs/mock

全局 css

Umi 中约定 src/global.css 为全局样式,如果存在此文件,会被自动引入到入口文件最前面。

https://umijs.org/zh-CN/docs/assets-css

route

创建 config/route.ts,然后再 umi 入口文件引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
export const routes = [
{
path: "/",
component: "@/pages/Home",
name: "首页",
icon: "HomeOutlined",
},
{
path: "/user",
name: "用户管理",
icon: "AliwangwangOutlined",
routes: [
{
path: "/user/list",
component: "@/pages/UserManage/UserList",
name: "用户列表",
},
],
},
{
path: "/power",
name: "权限管理",
icon: "PoweroffOutlined",
routes: [
{
path: "/power/powerlist",
component: "@/pages/PowerManage/RoleList",
name: "角色列表",
},
{
path: "/power/rolelist",
component: "@/pages/PowerManage/PowerList",
name: "权限列表",
},
],
},
{
path: "/goods",
name: "商品管理",
icon: "MediumOutlined",
routes: [
{
path: "/goods/list",
component: "@/pages/GoodManage/GoodList",
name: "商品列表",
},
{
path: "/goods/argu",
component: "@/pages/GoodManage/GoodArgu",
name: "分类参数",
},
{
path: "/goods/cate",
component: "@/pages/GoodManage/GoodCate",
name: "商品分类",
},
],
},
{
path: "/order",
name: "订单管理",
icon: "SketchOutlined",
routes: [
{
path: "/order/list",
component: "@/pages/OrderManage/OrderList",
name: "订单列表",
},
],
},
{
path: "/data",
name: "数据统计",
icon: "ScheduleOutlined",
routes: [
{
path: "/data/report",
component: "@/pages/DataStatis/DataReport",
name: "数据报表",
},
],
},
{
path: "/login",
component: "@/pages/Login",
// 不展示顶栏
headerRender: false,
// 不展示页脚
footerRender: false,
// 不展示菜单
menuRender: false,
// 不展示菜单顶栏
menuHeaderRender: false,
},
{
path: "*",
component: "@/pages/NotFound",
},
];

dva

  1. 配置
1
2
3
4
5
6
export default {
dva: {
immer: true,
hmr: false,
},
};
  1. model.ts

    约定式的 model 组织方式,不用手动注册 model

  • 创建一个对象并导出
  • 对象中有 4 个属性
    • namespace: string
    • state: {}
    • effects: { *函数: Effect }
      • 函数有两个参数
      • 函数({params},{call,put})
    • reducers: { *函数: Reducer }

一个小案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// model.ts
import { Effect, Reducer } from "@/.umi/plugin-dva/connect";
import * as service from "@/service";
import { message } from "antd";

interface UserModel {
namespace: string;
state: {
userList: unknown[];
total: number;
};
effects: {
getUserList: Effect;
changeState: Effect;
addUser: Effect;
};
reducers: {
GET_USER_LIST: Reducer;
GET_TOTAL: Reducer;
};
}

const userModel: UserModel = {
namespace: "user",
state: {
userList: [],
total: 0,
},
effects: {
*getUserList({ params }, { call, put }) {
const result = yield call(service.getUserListReq, params);
// console.log('xx', result);
result.data.data.users.forEach((item: any, index: number) => {
item.key = index;
});
yield put({
type: "GET_USER_LIST",
payload: result.data.data.users,
});
yield put({
type: "GET_TOTAL",
payload: result.data.data.total,
});
},
},
reducers: {
GET_USER_LIST(state, { payload }) {
state.userList = payload;
},
GET_TOTAL(state, { payload }) {
state.total = payload;
},
},
};

export default userModel;
  1. dispatch + connect
  • 在组件中 dispatch 激活 model 中的 effects
    • dispatch(type:’namespace/函数’,params:传给 model 的参数)
  • 在组件中从 umi 中引入 connect 高阶函数
    • 第一个参数回调函数 (state)=>state 用来导出

约定式路由

  1. 创建 src/srappers/auth.tsx
1
2
3
4
5
6
7
8
9
10
11
import { Redirect } from "umi";
import { useAuth } from "./hook";

export default (props: any) => {
const { isLogin } = useAuth();
if (!isLogin) {
return <div>{props.children}</div>;
} else {
return <div>没有权限</div>;
}
};
  1. useAuth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useState, useEffect } from "react";
import * as cookie from "@/utils/cookie";
export const useAuth = () => {
const [isLogin, setIsLogin] = useState(true);
useEffect(() => {
const username = cookie.getCookie("username");

if (username === "admin") {
setIsLogin(false);
}
}, []);

return { isLogin };
};
  1. 路由配置
1
+  wrappers: ['@/wrappers/auth'],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export function setCookie(name: string, value: string | number, n: number) {
const oDate = new Date();
oDate.setDate(oDate.getDate() + n);
document.cookie = name + "=" + value + ";expires=" + oDate;
}

export function getCookie(name: string) {
const str = document.cookie;
const arr = str.split("; ");
for (let i = 0; i < arr.length; i++) {
//console.log(arr[i]);
const newArr = arr[i].split("=");
if (newArr[0] == name) {
return newArr[1];
}
}
}

export function removeCookie(name: string) {
setCookie(name, 1, -1);
}

二次封装 axios 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//todo 封装数据请求
import axios from "axios";
import qs from "qs";
import * as cookie from "@/utils/cookie";
//todo 创建axios自定义实例
const ins = axios.create({
timeout: 20000,
// baseURL: "", 基础路径
});
//todo 创建axios拦截器
ins.interceptors.request.use(
(config) => {
//todo 携带token
// config.headers.commmon['userAgent'] = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
// config.headers.common["自定义"] = cookie.getCookie("token");
return config;
},
(error) => {
return Promise.reject(error);
}
);
//todo 封装request函数
interface Options {
url: string;
method?: string;
data?: any;
type?: string; //用于区别我是post请求的那种方式
}
export default function request(options: Options) {
const { url, method = "get", data = {}, type = "form" } = options;
switch (method.toUpperCase()) {
case "GET":
return ins.get(url, { params: data });
case "POST":
if (type === "json") {
//json提交
return ins.post(url, data);
}
if (type === "file") {
//文件提交
const p = new FormData();
for (const key in data) {
p.append(key, data[key]);
}
return ins.post(url, p);
}
// 进行表单提交 必须转参数【qs】
return ins.post(url, qs.stringify(data));
case "PUT":
return ins.put(url, data);
case "DELETE":
return ins.delete(url, { data });
default:
return ins.get(url, { params: data });
}
}

父组件绑定 flag 和 setflag 给 Modal 组件用于控制 Modal 的开关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Modal.tsx
import React, { useState, useEffect } from "react";
import { Modal, Button } from "antd";
import { ExclamationCircleTwoTone } from "@ant-design/icons";

interface P {
rmFlag: boolean;
setRmFlag: any;
}

const RmModal = (props: P) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const { rmFlag, setRmFlag } = props;
const handleOk = () => {
setRmFlag(false);
};

const handleCancel = () => {
setRmFlag(false);
};

useEffect(() => {
setIsModalVisible(rmFlag);
}, [rmFlag]);
return (
<div>
<Modal
title="提示"
visible={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
>
<p style={{ display: "flex", alignItems: "center" }}>
<ExclamationCircleTwoTone
twoToneColor="#FFA500"
style={{ fontSize: "28px", marginRight: "20px" }}
/>
此操作将永久删除该用户, 是否继续?
</p>
</Modal>
</div>
);
};

export default RmModal;