2021 年前端开发人员需要了解的 JS/React 规范
规范: 导入模块的顺序
以有组织的方式引入 ES6 模块将节省你查找任何找不到或不需要模块的时间
之前
import { PageHeaderWrapper } from "@ant-design/pro-layout";
import { CustomFormHOC } from "@/components/FormWidget";
import { Form, Card, Button, Divider, message, Alert } from "antd";
import { connect } from "dva";
import PropTypes from "prop-types";
import React, { Component } from "react";
import TransportInfo from "./TransportInfo";
import RecordInfo from "./RecordInfo";
import router from "umi/router";
import styles from "./index.less";
之后
// node_modules
import React, { Component } from "react";
import { Form, Card, Button, Divider, message, Alert } from "antd";
import { connect } from "dva";
import router from "umi/router";
import PropTypes from "prop-types";
import { PageHeaderWrapper } from "@ant-design/pro-layout";
// 项目公共模块
import { CustomFormHOC } from "@/components/FormWidget";
// 当前业务耦合模块
import TransportInfo from "./TransportInfo";
import RecordInfo from "./RecordInfo";
// 样式文件
import styles from "./index.less";
在之前的引入是无序的,一个文件可能会很乱,但是当你打开大量文件时候,尝试找到一个特定的包真的很难。
使用之后的方式对导入的包进行分组,通过空格行分割每个模块。又因为文件将保持一致,就可以删除注释了。
规范: 尽可能使用解构
另外一个重要的事情就是防止不必要的嵌套和重复(将对象的属性值保存为局部变量)
- 对象成员嵌套越深,读取速度也就越慢。所以好的经验法则是:如果在函数中需要多次读取一个对象属性,最佳做法是将该属性值保存在局部变量中,避免多次查找带来的性能开销(对象变量避免嵌套过深)
- 函数参数越少越好,如果参数超过两个,要使用 ES6 的解构语法,这样就不用考虑参数的顺序了
- 使用参数默认值 替代使用条件语句进行赋值
在大多数情况下,将大大提升可读性。
之前
const Page=(deliveryCompany,carrierName,driverInfo,driverInfo)=>{
return(
<Descriptions.Item label="供应商">{deliveryCompany || '未知'}</Descriptions.Item>
<Descriptions.Item label="承运商">{carrierName || '未知'}</Descriptions.Item>
<Descriptions.Item label="司机">{driverInfo.driver || '未知'}</Descriptions.Item>
<Descriptions.Item label="联系方式">{driverInfo.contact || '未知'}</Descriptions.Item>
);
}
之后
const Page=dataDetail=>{
const {
deliveryCompany = '未知',
carrierName = '未知',
driverInfo = { driver: '未知', contact: '未知' },
} = props;
const { driver, contact } = driverInfo;
return(
<Descriptions.Item label="供应商">{deliveryCompany}</Descriptions.Item>
<Descriptions.Item label="承运商">{carrierName}</Descriptions.Item>
<Descriptions.Item label="司机">{driver}</Descriptions.Item>
<Descriptions.Item label="联系方式">{contact}</Descriptions.Item>
);
}
规范: 变量和方法的命名约定
关于代码,有一点很重要,就是要知道一个方法将返回什么,或者通过变量名轻松理解变量的含义(变量语义化),比如
- JS 采用 Camel Case 小驼峰式命名
- 避免名称冗余,保证语义明确
- 每个常量都需要有意义的变量名(不然看代码的人不知道这个常量表示什么意思)
动词 | 含义 |
---|---|
can | 判断是否可执行某个动作 |
has | 判断是否含有某个值 |
is | 判断是否为某个值 |
get | 获取某个值 |
set | 设置某个值 |
之前
User.cat = true;
User.admin = true;
function NewUser(age) {
User.age = 150 - age;
return User;
}
function add_photo(photo) {
User.photo = photo;
}
之后
let user = {};
user.hasCat = true;
User.isAdmin = true;
const maxAge = 150;
function getUser(age) {
User.age = maxAge - age;
return User;
}
function setUserPhoto(photo) {
User.photo = photo;
}
在之后展示了如何在命名变量和方法保持一致性,在以下方面保持一致:
- 对于布尔类型使用:is, has,should 做前缀
- 对于方法使用 get/set 做前缀
- 变量和方法都使用驼峰命名
- 使用 ES6 的 const 定义常量
在之后,组件为注入一些公共变量做准备,比如 style, className, key 等等,使用展开操作,将一组公共属性传入容器。
规范: 删除弃用代码
很多时候有些代码已经没有用了,但是没有及时去删除,这样导致代码里面包括很多注释的代码块,好的习惯是提交代码前记得删除已经确认弃用的代码,例如:一些调试的console
语句、无用的弃用代码。 版本控制系统的存在是有原因的。如果想找旧代码就到 Git history 去找吧。
之前
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
之后
doStuff();
规范: 保持必要的注释
代码注释不是越多越好,保持必要的业务逻辑注释,至于函数的用途、代码逻辑等,要通过语义化的命令、简单明了的代码逻辑,来让阅读代码的人快速看懂。
规范: 使用 Async/Await 代替 Promise 和回调函数
Promise 是较回调而言更好的一种选择,但 ES7 中的 async 和 await 更胜过 Promise
在能使用 ES7 特性的情况下可以尽量使用他们替代 Promise
之前
require("request-promise")
.get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
.then(function (response) {
return require("fs-promise").writeFile("article.html", response);
})
.then(function () {
console.log("File written");
})
.catch(function (err) {
console.error(err);
});
之后
async function getCleanCodeArticle() {
try {
const request = await require("request-promise");
const response = await request.get(
"https://en.wikipedia.org/wiki/Robert_Cecil_Martin"
);
const fileHandle = await require("fs-promise");
await fileHandle.writeFile("article.html", response);
console.log("File written");
} catch (err) {
console.log(err);
}
}
规范: 如何捕获错误
错误抛出是个好东西!这使得你能够成功定位运行状态中的程序产生错误的位置。
使用 try catch
对捕获的错误不做任何处理是没有意义的。
代码中 try/catch
的意味着你认为这里可能出现一些错误,你应该对这些可能的错误存在相应的处理方案(前端监控方案之一)。
之前
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
之后
try {
functionThatMightThrow();
} catch (error) {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
}
使用 Promise 的 catch
之前
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
console.log(error);
});
之后
getdata()
.then((data) => {
functionThatMightThrow(data);
})
.catch((error) => {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
});
规范: React 相关
一些非常基础的规范这里就不说了, 说一些比基础高级一点的 ????
- 推荐使用函数式+Hooks 编写代码
遵守 Hooks 规则
不要在循环、条件和嵌套函数内调用 Hooks。当你想有条件地使用某些 Hooks 时,请在这些 Hooks 中写入条件。
错误代码
if (name !== "") {
useEffect(function persistForm() {
localStorage.setItem("formData", name);
});
}
应该这样做
useEffect(function persistForm() {
if (name !== "") {
localStorage.setItem("formData", name);
}
});
这条规则能确保每次渲染组件时都以相同的顺序调用 Hooks。这样一来,React 就能在多个 useState 和 useEffect 调用之间正确保留 Hooks 的状态。
使用 ESLint 的 React Hooks 插件
React 团队还创建了一个名为 eslint-plugin-react-hooks 的 ESLint 插件,以帮助开发人员在自己的项目中以正确的方式编写 React Hooks。这个插件能够帮助你在尝试运行应用程序之前捕获并修复 Hooks 错误。
它有两条简单的规则:
- react-hooks/rules-of-hooks
- react-hooks/exhaustive-deps
第一条规则只是强制你的代码符合我在第一个技巧中说明的 Hooks 规则。第二个规则,exhaustive-deps 用于实施 useEffect 的规则:effect 函数中引用的每个值也应出现在依赖项数组中。 例如,下面这个 userInfo 组件会触发 exhaustive-deps 警告,因为 userId 变量在 useEffect 内部被引用,但未在依赖项数组中传递:
function UserInfo({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
getUser(userId).then((user) => setUser(user));
}, []); // no userId here
return <div>User detail:</div>;
}
以正确的顺序创建函数组件
建议先使用 useState Hook 声明状态变量,然后使用 useEffect Hook 编写订阅,接着编写与组件作业相关的其他函数。
最后,你得返回要由浏览器渲染的元素:
function App() {
const [user, setUser] = useState(null);
const [name, setName] = useState("");
useEffect(() => {
console.log("component is mounted");
}, []);
return <h1>React component order</h1>;
}
useState 的用法可以和类组件的状态完全一致,不只用于单个值
许多 useState 示例会向你展示如何通过声明多个变量来声明多个状态:
const [name, setName] = useState("John Doe");
const [email, setEmail] = useState("johndoe@email.com");
const [age, setAge] = useState(28);
但是 useState 实际上既可以处理数组也可以处理对象。你依旧可以将相关数据分组为一个状态变量,如以下示例所示:
const [user, setUser] = useState({
name: "John Doe",
email: "john@email.com",
age: 28,
});
这里有一个警告。使用 useState 的更新函数更新状态时,以前的状态会替换为新状态。这与类组件的 this.setState 不同,后者的新类中,新状态会与旧状态合并:
const [user, setUser] = useState({
name: "John",
email: "john@email.com",
age: 28,
});
setUser({ name: "Nathan" });
// result { name: 'Nathan' }
为了保留以前的状态,你需要创建将当前状态值传递到自身中的回调函数来手动合并它。由于上面的示例已将 user 变量分配为状态值,因此可以将其传递给 setUser 函数,如下所示:
setUser((user) = > ({ ...user, name: 'Nathan' }));
// result is { name:'Nathan', email: 'john@email.com', age: 28 }
根据数据在应用程序生命周期中的变化情况,建议在各个值彼此独立时将状态拆分为多个变量。 但是对于某些情况,例如构建一个简单的表单,最好将状态分组在一起,以便更轻松地处理更改和提交数据。
简而言之,你需要在多个 useState 调用和单个 useState 调用之间保持平衡。
使用自定义 Hooks 共享应用程序逻辑
在构建应用程序时,你会注意到一些应用程序逻辑会在许多组件中一次又一次地使用。 随着 React Hooks 的发布,你可以将组件的逻辑提取到可重用的函数中作为自定义 Hooks,比如"非著名"阿里开源库 ahooks
使用 useContext 避免 prop drilling
prop-drilling 是 React 应用程序中的常见问题,指的是将数据从一个父组件向下传递,经过各层组,直到到达指定的子组件,而其他嵌套组件实际上并不需要它们。
React Context 是一项功能,它提供了一种通过组件树向下传递数据的方法,这种方法无需在组件之间手动传 props。父组件中定义的 React Context 的值可由其子级通过 useContext Hook 访问。
1. 组件声明
组件名称和定义该组件的文件名称建议要保持一致
之前
import FooterComponent from "./FooterComponent/index";
之后
import Footer from "./Footer/index";
2. React 中的命名
- 组件名称: 推荐使用大驼峰命名;
- 属性名称: React DOM 使用小驼峰命令来定义属性的名称,而不使用 HTML 属性名称的命名约定;
- style 样式属性: 采用小驼峰命名属性的 JavaScript 对象;
// 组件名称
MyComponent;
// 属性名称
onClick;
// 样式属性
backgroundColor;
3. 对齐
// 推荐
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// 如果组件的属性可以放在一行(一个属性时)就保持在当前一行中
<Foo bar="bar" />
// 多行属性采用缩进
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Quux />
</Foo>
// 不推荐
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
4. 引号
JSX 的属性都采用双引号,其他的 JS 都使用单引号 ,因为 JSX 属性 不能包含转义的引号, 所以当输入 "don't" 这类的缩写的时候用双引号会更方便
之前
<Foo bar='bar' />
<Foo style={{ left: "20px" }} />
之后
<Foo bar="bar" />
<Foo style={{ left: '20px' }} />
4. 为你的组件接收公共变量做好准备
之前
const UserInfo = (props) => {
const { name, sex, age } = props;
return (
<div>
<div>姓名:{name}</div>
<div>性别:{sex}</div>
<div>年龄:{age}</div>
</div>
);
};
之后
const UserInfo = (props) => {
const { name, sex, age, ...rest } = props;
return (
<div {...rest}>
<div>姓名:{name}</div>
<div>性别:{sex}</div>
<div>年龄:{age}</div>
</div>
);
};
7. 组件遵循单一职责原则
组件遵循单一职责原则(Single Responsibility Principle)可以让你轻松创建和贡献代码,并保持代码库的整洁。
即容器组件与傻瓜组件。
- 容器组件负责数据的请求与获取,props/state 的更新。
- 傻瓜组件只负责接收 props,抛出事件
之前
import React, { useState, useEffect } from "react";
import fetch from "isomorphic-fetch";
const UserInfo = (props) => {
const [user, setUser] = useState(null);
useEffect(() => {
async function getUser() {
const user = await fetch("/userAppi");
if (user) {
setUser(user);
}
}
getUser();
}, []);
const { name, age } = user;
return (
<div>
<div>{name}</div>
<div>{age}</div>
</div>
);
};
之后
import React, { useState, useEffect } from "react";
import { fetchUser } from "./api";
const UserInfo = (props) => {
const { user } = props;
const { name, age } = user;
return (
<div>
<div>{name}</div>
<div>{age}</div>
</div>
);
};
const UserInfoPage = (props) => {
const [user, setUser] = useState(null);
useEffect(() => {
async function getUser() {
const user = await fetchUser("/userAppi");
if (user) {
setUser(user);
}
}
getUser();
}, []);
return <UserInfo user={user} />;
};
附加:如果你正在使用类型检查器,请让它发挥作用。 如果你的团队选择使用类型检查器,那么使用严格模式很重要,以确保它能发挥作用,来达到使用它的目的。
interface UserInfoInterface {
name: string;
age: number;
}
const UserInfo = (props: UserInfoInterface) => {
const { user } = props;
const { name, age } = user;
return (
<div>
<div>{name}</div>
<div>{age}</div>
</div>
);
};
参考文档
- [译]提升你 react 和 js 编码的 5 个技巧
- 团队 React 代码规范制定
- 前端团队代码评审 CheckList 清单
- 京东凹凸实验室代码规范
最后
在日常工作中你还使用哪些 JavaScript规范呢?欢迎在评论区留下的你的见解!
觉得有收获的朋友欢迎点赞,关注一波!
往期文章
react 构建系列
- 企业级前端开发规范如何搭建 ????
- 「React Build」之集成 Webpack5/React17
- 「React Build」之集成 CSS/Less/Sass/Antd
- 「React Build」之集成图片/字体
- 「React Build」之集成 Redux/Typescript
- 「React Build」之使用 Redux-thunk 实现 Redux 异步操作
- 「React Build」之集成 React-Router/Antd Menu 实现路由权限
下一篇: Vue3 规范编码最佳实践