react 项目写法各异,这时候参考一个权威的样例,就会很有参考价值。无论是否示使用 ant design pro ,通过项目,都可以有很多的启发。
项目结构
如果拿不准如何搭建react 项目的目录结构,这时 ant design pro 的 src 项目结构,也许是一个参考的模板。
components 公共组件
components 公共组件,里面多个目录,每个目录为一个组件
- index.tsx 组件本身
- index.less 组件样式
如果该组件依赖子组件,可以在自身目录下创建
- NoticeIcon.tsx
- NoticeList.less
- NoticeList.tsx
样式文件,如果是模块化的模式,是可以被多个组件引用。
pages 页面组件
pages 页面在antd pro中相当于路由定义,虽然不用它这种模式,但路径即路由这种方式也可以学习的。
里面多个目录,每个目录为一个组件,或模块。
当该目录作为组件时:
- index.tsx 组件本身
- index.less 组件样式
当页面比较简单,也可以写在 pages 或模块下。
当存在子组件,可以写在组件目录的 components 下。
好处是,不容易跟其他页面组件混乱。而公共组件的子组件不需要components,因为他外层目录本来就是这个,而且公共组件一般引用的组件会少一些,也不必单独放在目录下。
样式
公共样式
可以看到有两个公共样式文件:
- global.less 公共样式及ant组件样式重置
- utils/utils.less 工具mixin
没有明确引入它们的地方。可以模仿文件组成
组件模块化样式
antd pro 所有组件都是外部 less 文件,通过模块化样式引入。这种用法也是比较标准的用法了,需要学习并使用。
看代码可见,只要是类名,都是模块化的,即使父级用了 classNama={styles.a}
,子级也不能通过 classNama="b"
使用。
类名下的标签、伪类等,可以生效。
.a {
.b {
input {
}
.c {
}
}
}
应该使用:
<div className={styles.a}>
<div className={styles.b}>
<div className={styles.c}>
</div>
</div>
</div>
类名复杂的:
import classNames from 'classnames';
const inputClass = classNames(styles.input, {
[styles.show]: searchMode,
});
样式穿透
:global(内部类名)
即可实现穿透
.lang {
width: 100%;
height: 40px;
line-height: 44px;
text-align: right;
:global(.ant-dropdown-trigger) {
margin-right: 24px;
}
}
如果是多个穿透样式:
.tabs {
:global {
.ant-tabs-nav-list {
margin: auto;
}
.ant-tabs-nav-scroll {
text-align: center;
}
.ant-tabs-bar {
margin-bottom: 0;
}
}
}
ts 使用
组件 Props 类型定义
组件 Props 的类型,跟当前组件关联性高,直接在定义组件前定义。
export interface UpdateFormProps {
onCancel: (flag?: boolean, formVals?: FormValueType) => void;
onSubmit: (values: FormValueType) => Promise<void>;
updateModalVisible: boolean;
values: Partial<TableListItem>;
}
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
};
数据格式定义
在 services 目录下定义的接口,数据格式定义在 API.d.ts
中
declare namespace API {
// 用户基本信息
export interface CurrentUser {
avatar?: string;
name?: string;
title?: string;
group?: string;
signature?: string;
tags?: {
key: string;
label: string;
}[];
userid?: string;
access?: 'user' | 'guest' | 'admin';
unreadCount?: number;
}
}
d.ts 结尾的文件会被 TypeScript 默认导入到全局
export async function query() {
return request<API.CurrentUser[]>('/api/users');
}
全局类型避免了复杂的引用
为 Window 增加参数
在 /src/typings.d.ts
文件做一些全局的类型
interface Window {
ga: (
command: 'send',
hitType: 'event' | 'pageview',
fieldsObject: GAFieldsObject | string,
) => void;
reloadAuthorized: () => void;
}
定义全局变量的类型(变量在其他地方被全局注入,如环境变量)
declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
数据请求
数据请求接口:
export async function updateRule(params: TableListParams) {
return request('/api/rule', {
method: 'POST',
data: {
...params,
method: 'update',
},
});
}
把处理接口的操作,封装为函数。这类增、删、改的函数不需要放在组件内,返回成功或失败。即简化组件内的函数,不是必要的就可以提取到外面。
/**
* 更新节点
* @param fields
*/
const handleUpdate = async (fields: FormValueType) => {
const hide = message.loading('正在配置');
try {
await updateRule({
name: fields.name,
desc: fields.desc,
key: fields.key,
});
hide();
message.success('配置成功');
return true;
} catch (error) {
hide();
message.error('配置失败请重试!');
return false;
}
};
直接在回调函数中,即可调用并处理成功失败的逻辑。
<UpdateForm
onSubmit={async (value) => {
const success = await handleUpdate(value);
if (success) {
handleUpdateModalVisible(false);
setStepFormValues({});
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleUpdateModalVisible(false);
setStepFormValues({});
}}
updateModalVisible={updateModalVisible}
values={stepFormValues}
/>
全局状态
定义全局状态
在 app.tsx 中,定义了全局状态。可见这就是一个类似于 reducer 函数的定义。
- 定义了 state : settings 和 currentUser
- 定义了异步 action : fetchUserInfo
export async function getInitialState(): Promise<{
settings?: LayoutSettings;
currentUser?: API.CurrentUser;
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}> {
const fetchUserInfo = async () => {
try {
const currentUser = await queryCurrent();
return currentUser;
} catch (error) {
history.push('/user/login');
}
return undefined;
};
// 如果是登录页面,不执行
if (history.location.pathname !== '/user/login') {
const currentUser = await fetchUserInfo();
return {
fetchUserInfo,
currentUser,
settings: defaultSettings,
};
}
return {
fetchUserInfo,
settings: defaultSettings,
};
}
获取和改变全局状态
组件中,可以直接获取和改变全局状态:
const { initialState, setInitialState } = useModel('@@initialState');
setInitialState({ ...initialState, currentUser: undefined });
可见在新版中,dva那一套已经被隐藏或弃用,全局状态已经非常的简洁。