Ant Design Pro

Ant Design Pro笔记

研发资产:区块和模版

什么是区块?

区块就是代码片段

区块是Ant Design Pro中,研发资产的一种,它是一系列快速搭建页面的代码片段,它可以帮助你快速的在项目中初始化好一个页面,帮助你更快速的开发代码。当前的区块都是页面级别的区块,你可以理解为它是一些项目中经常会用到的典型页面的模板,使用区块其实相当于从已有的项目中复制一些页面的代码到你当前的项目中。

  • 以前开发一个页面:创建 JS -> 创建 CSS -> 创建 Model -> 创建 service -> 写页面组件。
  • 现在开发一个页面:下载区块 -> 基于区块初始化好的页面组件修改代码。

什么是模版?

在 Pro 中资产被分为了两种,区块和模板。区块可以类比为一个组件,而模板代表一个页面。区块现在支持所有 antd 中的 demo,可以更加快速的将 demo 导入到项目中去。

布局

页面整体布局是一个产品最外层的框架结构,往往会包含导航、页脚、侧边栏、通知栏以及内容等。在页面之中,也有很多区块的布局结构。在真实项目中,页面布局通常统领整个应用的界面,有非常重要的作用。

在 Ant Design Pro 中,我们抽离了使用过程中的通用布局,都放在 layouts 目录中,分别为:

  • BasicLayout:基础页面布局,包含了头部导航,侧边栏和通知栏。
  • UserLayout:抽离出用于登录注册页面的通用布局。
  • BlankLayout:空白的布局

路由和菜单

在这一部分,脚手架通过结合一些配置文件、基本算法及工具函数,搭建好了路由和菜单的基本框架,主要涉及以下几个模块/功能:

  • 路由管理 通过约定的语法根据在 config.ts 中配置路由。

  • 菜单生成 根据路由配置来生成菜单。菜单项名称,嵌套路径与路由高度耦合。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 二级菜单管理页面
    {
    path: '/admin',
    name: 'admin',
    icon: 'crown',
    access: 'canAdmin',
    component: './Admin',
    routes: [
    {
    path: '/admin/sub-page',
    name: 'sub-page',
    icon: 'smile',
    component: './Welcome',
    },
    ],
    },
  • 面包屑 组件 PageHeaderWrapper 中内置的面包屑,也可通过 RouteContext 提供的信息自定义生成。

路由

目前脚手架中所有的路由都通过 config.ts 来统一管理,在 umi 的配置中我们增加了一些参数,如 nameiconhideChildrenInMenuauthority,来辅助生成菜单。其中:

  • hideInMenu 可以在菜单中不展示这个路由,包括子路由。
  • authority 用来配置这个路由的权限,如果配置了将会验证当前用户的权限,并决定是否展示。

配置中的 name 和菜单实际展示的不同,这是因为我们使用了全球化组件的原因,具体参见 i18n

菜单

菜单根据 config.ts 生成。

如果你的项目并不需要菜单,你可以在 src/layouts/BasicLayout.tsx 中设置 menuRender={false}

从服务器请求菜单

你可以在 src/layouts/BasicLayout.tsx 中修改 menuDataRender,并在代码中发起 http 请求,只需服务器返回下面格式的 json 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const [menuData, setMenuData] = useState([]);

useEffect(() => {
// 这里是一个演示用法
// 真实项目中建议使用 dva dispatch 或者 umi-request
fetch('/api/example.json')
.then(response => response.json())
.then(data => {
setMenuData(data || []);
});
}, []);

...

return (
<ProLayout
// ...
menuDataRender={() => menuData}
// ...
/>
);

menuData 数据格式如下,ts 定义在此:MenuDataItem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"path": "/dashboard",
"name": "dashboard",
"icon": "dashboard",
"children": [
{
"path": "/dashboard/analysis",
"name": "analysis"
},
{
"path": "/dashboard/monitor",
"name": "monitor"
},
{
"path": "/dashboard/workplace",
"name": "workplace"
}
]
}
// ....
]

注意 path 必须要在 config.ts 中定义。(约定式路由不需要,只需页面真实有效即可)注意 如果没有选用 typescript,config.js 中菜单的嵌套写法,需要把 "children" 换成 "routes"

包屑

面包屑由 PageHeaderWrapper 实现,Layout 将 根据 MenuData 生成的 breadcrumb,并通过 PageHeaderWrapper 将其展现。 PageHeaderWrapper 封装至 Ant Design 的 PageHeader,api 完全相同。

添加页面/布局

添加空白布局页面

step 1

修改config.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
routes: [
{
path: '/user', // 设置上级路由
layout: false, // 设置为false,意味着是自定义的UserLayout或者BlankLayout布局
routes: [
{
name: 'login',
path: '/user/login',
component: './user/login',
},
// 新增路由
{
name: 'newpage',
icon: 'smile',
path: '/user/newpage',
component: './user/newpage',
},
],
},
// ...
}

step 2

添加静态资源

image-20200627204703187

index.js

1
2
3
4
export default () => {
// eslint-disable-next-line react/react-in-jsx-scope
return <div>This is the New Page!</div>;
};

Index.less

1
@import '~antd/lib/style/themes/default.less'; // 引入默认样式

输入URL即可打开对应页面

image-20200627204938886

向空白布局中添加组件

使用umiJS可视化工具,可以直接向页面中添加组件即可。

image-20200627210832996

效果如下

image-20200627210743980

添加带布局的页面

同样使用umiJS的可视化布局方法,就能加载预先设置好的带有布局的页面。

1
2
3
4
5
6
7
8
routes: [
{
name: '个人中心',
icon: 'smile',
path: '/accountcenter',
component: './AccountCenter',
},
]

添加之后的静态资源情况

image-20200627213113086

实际效果

image-20200627212935003

添加空白页面再编辑的方法

先添加一个空白页面

image-20200627213228066

添加之后的静态资源情况

image-20200627213304929

就会得到这样一个空白页面

image-20200627213936816

然后再进入空白页面,向空白页面中添加组件。

image-20200627213650169

最终就得到一个含有组件的页面了。

image-20200627213837543

修改样式

为什么要用CSS Modules?

传统CSS主要问题:

  • 全局污染 —— CSS 文件中的选择器是全局生效的,不同文件中的同名选择器,根据 build 后生成文件中的先后顺序,后面的样式会将前面的覆盖,越早申明优先级越高;
  • 选择器复杂 —— 为了避免上面的问题,我们在编写样式的时候不得不小心翼翼,类名里会带上限制范围的标识,变得越来越长,多人开发时还很容易导致命名风格混乱,一个元素上使用的选择器个数也可能越来越多。

为了解决以上问题,脚手架默认使用 CSS Modules 模块化方案,先来看下在这种模式下怎么写样式。

less文件

1
2
3
4
5
6
// exmaple.less
.title {
color: @heading-color;
font-weight: 600;
margin-bottom: 16px;
}

ts/tsx文件

1
2
3
// exmaple.ts
import styles from './exmaple.less'
export default ({title}) => <div className={{style.title}}>{{title}}</div>

在设置 className 时,用一个对象属性取代了原来的字符串,属性名跟 less 文件中对应的类名相同,对象从 less 文件中引入。.title 只会在本文件生效,你可以在其他任意文件中使用同名选择器,也不会对这里造成影响。不过有的时候,我们就是想要一个全局生效的样式呢?可以使用 :global

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// exmaple.less
.title {
color: @heading-color;
font-weight: 600;
margin-bottom: 16px;
}

/* 定义全局样式 */
:global {
.footer {
color: #ccc;
}
.sider {
background: #ebebeb;
}
}

怎么用CSS Modules清楚了,原理是怎样的呢?

CSS Modules机制对每个类名(非 :global 声明)按照一定规则进行转换,保证它的唯一性。如果在浏览器里查看这个示例的 dom 结构,你会发现实际渲染出来是这样的:

1
<div class="title___3TqAx">title</div>

类名被自动添加了一个 hash 值,这保证了它的唯一性。需要注意的关键点

  • CSS Modules 只会对 className 以及 id 进行转换,其他的比如属性选择器,标签选择器都不进行处理,推荐尽量使用 className。
  • 由于不用担心类名重复,你的 className 可以在基本语意化的前提下尽量简单一点儿。

详细教程有哪些?

国际化

Ant Desing Pro默认使用react-intl实现组件国际化。

如果在工程中直接使用中文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
routes: [     
// ...
{
name: 'newpage',
icon: 'smile',
path: '/user/newpage',
component: './user/newpage',
},{
name: '注册页',
icon: 'smile',
path: '/user/forget',
component: './user/forget',
},
// ...
]

就会出现各种报错。

1
2
3
devScripts.js:5035 [React Intl] Missing message: "menu.注册页" for locale: "zh-CN", using default message as fallback.

devScripts.js:5035 [React Intl] Missing message: "menu.newpage" for locale: "zh-CN", using default message as fallback.

这时候就要去修改

src/locales/zh-CN|en-US/ 路径下的对应配置文件。

src/locales/zh-CN/menu.ts

1
2
3
4
5
6
7
export default {
'menu.welcome': '欢迎',
'menu.more-blocks': '更多区块',
'menu.home': '首页',
// ...
'menu.newpage': "新页面",
};

src/locales/en-US/menu.ts

1
2
3
4
5
6
7
export default {
'menu.welcome': 'Welcome',
'menu.more-blocks': 'More Blocks',
'menu.home': 'Home',
// ...
'menu.newpage': "New Page",
};

重新加载之后,报错就会消失。

与后端交互

一个完整的前端 UI 交互到服务端处理流程是这样的:

  1. UI 组件交互操作;
  2. 调用 model 的 effect;
  3. 调用统一管理的 service 请求函数;
  4. 使用封装的 request.ts 发送请求;
  5. 获取服务端返回;
  6. 然后调用 reducer 改变 state;
  7. 更新 model。

统一的请求处理都放在 services 文件夹中,并且一般按照 model 维度进行拆分文件,如:

1
2
3
4
services/
user.ts
api.ts
...

其中,utils/request.ts 是基于 fetch 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。具体可以参看

例如在 services 中的一个请求用户信息的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// services/user.ts
import request from '../utils/request';

export async function query() {
return request('/api/users');
}

export async function queryCurrent() {
return request('/api/currentUser');
}

// models/user.ts
import { queryCurrent } from '../services/user';
...
effects: {
*fetch({ payload }, { call, put }) {
...
const response = yield call(queryCurrent);
...
},
}

异步请求

在处理复杂的异步请求的时候,很容易让逻辑混乱,陷入嵌套陷阱,所以 Ant Design Pro 的底层基础框架 dva 使用 effect 的方式来管理同步化异步请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
effects: {
*fetch({ payload }, { call, put }) {
yield put({
type: 'changeLoading',
payload: true,
});
// 异步请求 1
const response = yield call(queryFakeList, payload);
yield put({
type: 'save',
payload: response,
});
// 异步请求 2
const response2 = yield call(queryFakeList2, payload);
yield put({
type: 'save2',
payload: response2,
});
yield put({
type: 'changeLoading',
payload: false,
});
},
},
  1. Ajax怎么配置?
  2. 数据怎么传到后台?

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!