React 编码规范
JavaScript / TypeScript 写法,和本指南中 JavaScript 中的部分基本吻合。这里主要说下 React JSX 语法下的一些规范。下文仅适用于 React 和类似用法(Preact / SolidJS)的框架。
引入模块
使用 import
引入模块将根据其类型和作用进行分类,还根据其来源(框架,外部包,项目内引用)来编写。
引用模块的类型,保罗是这样进行分类的:
- 框架引用(例如 React)
- 样式和 UI 组件引用(例如 Ant Design)
- 工具类库引用(例如 Lodash 或者是自己封装的工具函数)
- TypeScript 类型引用和适用于该文件内函数/组件的类型定义
- 组件函数本体
// React
import React, { useRef } from "react";
import useRequest from "@/hooks/useRequest";
// UI
import styles from "./DatePicker.module.less";
import { Card, Button } from "antd";
// Tool
import { isLogined, parseJWTData } from "@/utils/tool";
// Interface
import { IUserInfo } from "@/types/user";
interface UserCardProps {
user: IUserInfo
}
// Component
function UserCard(props) => {
// ...
}
来源的引入顺序是:外部包、项目内部模块,例如这段代码,React 作为外部包,其优先级在前。
// React
import React, { useRef } from "react";
import useRequest from "@/hooks/useRequest";
引入样式
如果是全局样式,可在入口文件(一般为 app.tsx
或 main.tsx
)里直接使用 import
进行导入,而无需设置任何名称。
import "./global.css";
如果是局部样式,保罗推荐使用 CSS Modules 形式引入样式,再结合 Less/Sass 等预编译器的嵌套编写方式,可在简化类选择器命名的同时,不干扰任何采用相同命名的其他组件。关于 Less 的书写规范,可以查阅本文档的对应页面。
// 👍 推荐的
import styles from "./DatePicker.module.less";
<div className={styles.picker}>
...
</div>
// 👎 不推荐的
import "./DatePicker.module.less";
<div className="paul-date-picker">
...
</div>
导出模块
一个文件导出一个模块(例如 React 的单个组件),默认使用 export default {name}
形式。
// Component
function UserCard(props) => {
...
}
export default UserCard;
一个文件导出多个模块,使用 export const {name}
(推荐)或 export { name, name }
形式。
export const isLogined = () => {
...
}
export const parseJWTData = () => {
...
}
或者是
const isLogined = () => {
...
}
const parseJWTData = () => {
...
}
export { isLogined, parseJWTData };
组件参数和回调
如果组件没有任何 children
,你需要用 />
形式进行结尾。就和 HTML 的 input
img
等元素的写法 <input type="text" />
差不多。
// 👍 推荐的
<ModalTable data={props.data} />
// 👎 不推荐的
<ModalTable data={props.data}></ModalTable>
如果一个组件同时有多个参数和回调函数,同类型的多个参数,可放一行,太长再换行。
// data 和 variant 是同类只入不出的参数,放一行
<Modal className={styles.modal} visible={visible}
data={stat.data[index]} variant={stat.variant}
onChange={onModalChange} onSubmit={onModalSubmit}
/>
// 只有一个 `onXXX` 函数,如果比较长就可以单独占用一行。
<Select className="w-full" value={stat.data.currency}
onChange={value => setProperty("currency", value)}
>
<Select.Option value="MYR">MYR (RM)</Select.Option>
</Select>
用更少的 props
做同样的事,数据类参数,可使用 TypeScript
限定类型,而不是拆分为多个参数传入组件。
// 👍 推荐的
<UserCard data={user} onChange={updateUserRequest} />
// 👎 不推荐的
<UserCard name={user.name} avatar={user.avatar} email={user.avatar}
onChange={updateUserRequest}
/>
响应式状态定义
同类数据归类
使用「自定义 Hooks」将同类数据进行归类,而不应该一个数据一个 useState
。
如果采用后者进行开发,一旦后期程序的业务逻辑疯狂扩张,就会极其混乱,你需要同时执行多个 setState
函数。且大幅度降低了可读性,且容易产生设置遗漏。
// 👍 推荐的
// 用来提交接口的参数,这些都是默认的值,可以通过 setParams 修改局部数据,也可使用 resetParams 还原为初
const [params, setParams, resetParams] = useParams({
page: 1,
type: "all"
});
setParams({ page: 1, type: "pending" }); // 修改 type,回到第一页
// 👎 不推荐的
// 状态分离,你并不清楚谁和谁有关系,哪个 State 给哪个组件引用
const [page, setPage] = useState(1);
const [type, setType] = useState("all");
const [data, setData] = useState([]);
setPage(1);
setType("all");
setData([]);
你可以看看这个实际代码,一定会觉得蛋疼了
初始化状态
const [audit, setAudit] = useState("");
const [domain, setDomain] = useState<string | number>("");
const [note, setNote] = useState("");
const [trackingNumber, setTrackingNumber] = useState("");
const [trackingUrl, setTrackingUrl] = useState("");
修改状态
// 这里面漏了一个 setAudit,你一眼能看出来吗?
const onShow = () => {
setNote(record.t3_note);
setDomain(record.to_t3_domain);
setTrackingUrl(record.tracking_url);
setTrackingNumber(record.tracking_number);
}
函数定义
一次性函数分离
简单来说,就是一个负责「传入一个对象,返回一个新的对象」的功能函数,大多用于数据过滤、筛选返回新状态等,这种函数不建议放在 React 组件内部,因为 React 的渲染机制,每执行一次都会重新生成一个这样的函数,这很显然是没有意义的。
// 👍 推荐的
// 可重复使用的函数不放在组件内部
const moodsMap = ["一般", "开心", "兴奋", "紧张", "无聊", "难过", "愤怒", "烦躁", "郁闷", "疲惫", "失落"];
const parseMood = (key: number) => {
return moodsMap[key] ? moodsMap[key] : "未定义";
}
function Plan({ key }) {
return (
<p>我的心情:{parseMood(key)}</p>
);
}
// 👍 推荐的
// 工具函数单独抽离一个文件存储
import { parseMood } from "@/utils/note";
function Plan({ key }) {
return (
<p>我的心情:{parseMood(key)}</p>
);
}
// 👎 不推荐的
// 可重复使用的函数放在了组件内部,每次重新渲染都重新生成一个新的函数
function Plan({ key }) {
const moodsMap = ["一般", "开心", "兴奋", "紧张", "无聊", "难过", "愤怒", "烦躁", "郁闷", "疲惫", "失落"];
const parseMood = (key: number) => {
return moodsMap[key] ? moodsMap[key] : "未定义";
}
return (
<p>我的心情:{parseMood(key)}</p>
);
}