前言
在这篇文章中,我对 React 代码规范
进行了总结,分享出我认为的最佳实践,供大家参考。
代码书写规范
1. jsx 标签的书写规范
// 当标签没有子元素时,始终使用自闭合标签
<Footer data={data} />
// 当标签有子元素时,选择使用双标签闭合
<Footer
bar={bar}
data={data}
>
<Button />
</Footer>
2. jsx 属性对齐方式
// 如果没有属性,在闭合标签前添加一个空格
<Footer />
// 如果是多个属性,直接属性换行对齐的方式,同时闭合标签是换行的
<Footer
bar={bar}
data={data}
/>
// 如果可以放在一行,放在一行上即可
<Footer bar={bar} />
// 如果是多行,采用缩进的书写方式
<Footer
bar={bar}
data={data}
>
<Button />
</Footer>
3. 样式写法
(1) 引入外联样式,一经加载将全局有效,类名相同时样式之间会互相影响,造成全局污染(不采用)
// bad 🔴
{
import React from 'react';
import './index.less';
const TestCom = () => (
<div className='out-css'>
<p className='text'>引入外联样式</p>
</div>
);
export default TestCom;
}
(2) 引入外联模块化样式的类名不会全局污染
将css文件作为一个模块引入,这个模块中的所有css,只作用于当前组件;不会影响当前组件的后代组件;样式之间不会有冲突
// good ✅
{
import React from 'react';
import styles from './index.less';
const TestCom = () => (
const flag = true;
{/* 无中划线的类名这样使用 */}
<div className={styles.wrapTest}>
{/* 有中划线的类名这样使用 */}
<span className={styles['span-test']}>引入外联模块化样式</span>
{/* 动态判断样式这样使用 */}
<span className={`${flag ? styles['flag-true'] : styles['flag-false']}`}>引入外联模块化样式</span>
</div>
);
export default TestCom;
}
4. 命名规则
基础规则:变量,类名,文件命名需要有实际含义(语意化),不能使用系统字段进行命名(关键字、保留字等)
// 组件名称使用大驼峰命名
import ReservationCard from './ReservationCard';
// 属性名称使用小驼峰命名
<Footer
data={data}
showStatus={showStatus}
onClickSubmit={onClickSubmit}
/>;
// 自定义事件名称使用小驼峰命名
const onClickSubmit = () => {};
// 自定义函数,动词开头,明确函数意图
const sendEmailToUser = (user) => {... }; // good ✅
const emailUser = (user) => {... }; // bad 🔴
// 自定义常量命名全部大写,下划线分割,避免使用没有意义的命名(语意化)
const CHART_TYPE = 'line'; // 图表类型
const TIME_OUT = 1000 * 30; // 超时时间
// 自定义变量命名使用小驼峰或者下划线命名,不能使用脚本语言中保留的关键字、保留字,避免使用没有意义的命名(语意化)
const status = true; // good ✅
const showStatus = true; // good ✅
const show_status = true; // good ✅
const class = {}; // bad 🔴
const if = true; // bad 🔴
const a = 'Sunday'; // bad 🔴
const week = 'Sunday'; // good ✅
5. 公共方法函数必须有注释说明(author 非必写)
/**
* @name getFunc
* @desc 方法描述
* @param {string, object}
* @return bool
* @author xxx
*/
export const getFunc = (param1, param1) => {
return false;
};
6. key 属性设置
Key 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。
在使用 map() 渲染时,要在列表中使用 key,主要是为了复用列表元素,加快 diff 的过程,提高渲染的效率。
⚠️ 注意事项:
- key 值一定要和具体的元素一一对应;
- 尽量不要用数组的 index 去作为 key;(数组的改变,会导致索引对应的数组内容不正确)
// bad 🔴
{
todos.map((todo, index) => <Todo {...todo} key={index} />);
}
// good ✅
{
todos.map((todo, index) => <Todo {...todo} key={todo.id} />);
}
7. Hooks 相关
(1) 不要缺少 useEffect 依赖
默认情况下,它总是在每次重新渲染时运行。但这样就可能会导致不必要的渲染。我们可以通过给 useEffect 设置恰当的依赖数组来避免这些不必要的渲染。
⚠️ 注意事项:不是所有的依赖都必须放到依赖数组中。只有一种情况,需要把变量放到依赖数组中,那就是当该变量变化时,需要触发 useEffect 函数执行。而不是因为 useEffect 中用到了这个变量!
const Counter = () => {
const [count, setCount] = useState(0);
const showCount = (count) => {
console.log('Count', count);
};
// bad 每次渲染之后都会执行 🔴
useEffect(() => {
showCount(count);
});
// good 第一次渲染完成之后运行,只执行一次 ✅
useEffect(() => {
showCount(666);
}, []);
// good 只有当变量count发生改变之后才会重新执行 ✅
useEffect(() => {
showCount(count);
}, [count]);
return <div>Counter: {count}</div>;
};
(2) 不要忘记清理副作用
所谓副作用,就是对函数之外造成影响,指那些会影响其他组件以及在渲染过程中无法完成的操作,如:定时器、事件监听、操作 dom 等。
const Counter = () => {
const barRef = useRef<any>();
const [count, setCount] = useState(0);
const resizeEvent = () => {
console.log('resizeEvent');
};
useEffect(() => {
const myChart = echarts.init(barRef.current);
myChart.setOption({});
const timer = setTimeout(() => {
setCount((count) => count + 1);
}, 100);
window.addEventListener('resize', resizeEvent);
return () => {
myChart.dispose(); // 销毁echarts实例
clearTimeout(timer); // 清除定时器
window.removeEventListener('resize', resizeEvent); // 移除事件监听
};
}, []);
return <div>Counter: {count}</div>;
};
(3) 使用多个 Effect 实现关注点分离
使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。
使用多个 effect,这会将不相关逻辑分离到不同的 effect 中。
const BusinessLogic = () => {
const [count, setCount] = useState(0);
const [status, setStatus] = useState(false);
const initData = () => {};
useEffect(() => {
const showCount = () => {};
initData();
setCount(666);
showCount();
// do something...
}, []);
useEffect(() => {
const handleStatusChange = () => {};
initData();
setStatus(true);
handleStatusChange();
// do something...
}, []);
// ...
};
(4) 只在最顶层使用 Hook,不要在循环、条件或嵌套函数中调用 Hook
不要在循环,条件或嵌套函数中调用 Hook,确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
const BusinessLogic = () => {
const [count, setCount] = useState(0);
const [status, setStatus] = useState(false);
// 🔴 不能在 Hook 调用之前 return
if (!status) {
return '状态不能为空!';
}
// 🔴 不要在条件语句中使用 Hook
if (status) {
useEffect(() => {
setCount(666);
}, []);
}
// ✅ 将条件判断放置在 effect 中
useEffect(() => {
if (status) {
setCount(666);
}
}, []);
// ✅ 保证 Hook 在最顶层使用
if (!status) {
return '状态不能为空!';
}
};
8. 业务逻辑代码处理
(1) 适当的注释
对于复杂的业务逻辑代码,应该进行适当的注释,防止自己遗忘代码细节,同时也让其他同事能粗略看懂,提高代码的可维护性。
(2) 适当的抽离
对于复杂的业务逻辑代码,视情况而定,进行模块化,抽离成单独的函数方法或者组件,让主文件结构看起来更加清晰,提高代码的可读性。
9. 组件复杂度
(1) 逻辑拆分和抽象
如果一个组件做的事情太多,应依照一定的规则和条件,去适当提取一些逻辑,将其拆分为更小的组件。
代码行数并不是一个客观的衡量标准,更多是需要考虑责任划分和抽象。
(2) props 传参数量
如果超过 5 个 props,就该考虑是否拆分该组件。在某些情况下,这是需要对组件进行重构的标志。
⚠️ 注意:组件使用的 props 越多,重新渲染的理由就越多。
10. 一个文件声明一个组件
尽管可以在一个文件中声明多个 React 组件,但是最好不要这样做;推荐一个文件声明一个 React 组件,并只导出一个组件
代码格式化规范
对应编辑器插件 Prettier 配置
代码格式化工具 Prettier 中文官网地址 www.prettier.cn
1. 代码缩进 2 个空格符(tabWidth: 2)
const tabWidth = {
a: 1,
b: 2,
};
2. 使用单引号(singleQuote: true)
const singleQuote = {
a: '1',
b: '2',
};
3. 结尾添加分号(semi: true)
const a = 1;
const b = 2;
4. object 对象里面的 key 和 value 值和括号间加空格(bracketSpacing: true)
const a = { foo: 'bar' };
const fun = ({ foo: 'bar' }) => {};
5. 单行代码超过 80 个字符需做换行操作(printWidth: 80)
配置编辑器(VScode为例)
(1)创建一个 .prettierrc 文件,并配置规则,让编辑器和其他工具知道你正在使用 Prettier
{
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"semi": true,
"bracketSpacing": true,
"overrides": [
{
"files": ".prettierrc",
"options": {
"parser": "json"
}
}
]
}
(2)创建一个 .prettierignore 文件,让 Prettier CLI 和编辑器知道哪些文件不能格式化
**/*.svg
**/*.ejs
**/*.html
package.json
.umi
.umi-production
.umi-test
/dist
/build
/public
(3)在IDE中安装 Prettier-Code Formater 插件
(4)找到IDE中设置
模块,搜索 format On Save,勾选上这个就可以了