---
group: Coding Style
order: 5
---
# React Coding Specification
## 1 编码风格
### 1.1 缩进
- 1.1.1 `mandatory` JSX 语法使用 2 个空格缩进。eslint: [react/jsx-indent](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md) [react/jsx-indent-props](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md) [react/jsx-closing-tag-location](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-tag-location.md)
对于 JSX 语法,遵循与 JS 规约和 HTML 规约一致的 2 个空格缩进,不要使用 4 空格或 tab 缩进:
```jsx static
// bad
// good
```
### 1.2 空格
- 1.2.1 `mandatory` 自闭合标签的斜线前有且仅有一个空格。eslint: [no-multi-spaces](https://eslint.org/docs/rules/no-multi-spaces) [react/jsx-tag-spacing](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-tag-spacing.md)
```jsx static
// bad
// very bad
// bad
// good
```
- 1.2.2 `mandatory` JSX 行内属性之间仅有一个空格。eslint: [react/jsx-props-no-multi-spaces](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-props-no-multi-spaces.md)
同一行中标签和属性之间、属性之间只有一个空格。
```jsx static
// bad
// good
```
- 1.2.3 `mandatory` JSX 属性的大括号内部两侧无空格。eslint: [react/jsx-curly-spacing](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md)
```jsx static
// bad
// good
```
- 1.2.4 `mandatory` 不要在 JSX 属性的等号两边加空格。eslint: [jsx-equals-spacing](https://eslint.org/docs/rules/jsx-equals-spacing)
```jsx static
// bad
;
// good
;
```
### 1.3 引号
- 1.3.1 `mandatory` JSX 属性使用双引号,不要使用单引号。eslint: [jsx-quotes](https://eslint.org/docs/rules/jsx-quotes)
为什么?HTML 属性通常使用双引号而不是单引号,因此 JSX 属性沿用了这种约定。
其他 JS 使用单引号。
```jsx static
// bad
// good
// bad
// good
```
### 1.4 小括号
- 1.4.1 `mandatory` 多行的 JSX 标签需用小括号包裹。eslint: [react/jsx-wrap-multilines](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.md)
```jsx static
// bad
render() {
return ;
}
// good
render() {
return (
);
}
// good - 单行的 jsx 无需加圆括号
render() {
const body =
hello
;
return {body};
}
```
### 1.5 标签
- 1.5.1 `mandatory` 无子元素的标签需写成自闭合标签。eslint: [react/self-closing-comp](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md)
```jsx static
// bad
// good
```
- 1.5.2 `mandatory` 标签属性的换行。eslint: [react/jsx-max-props-per-line](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md) [react/jsx-first-prop-new-line](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-first-prop-new-line.md)
对 JSX 标签属性的换行,遵循以下规则:
- 标签名和它的属性可以写在一行,前提是不超过单行最大 100 字符数的限制
- 如果标签有多个属性,且存在换行,则每个属性都需要换行独占一行
```jsx static
// bad - 属性应全部换行,或全部跟组件名写在一行
// good
// good - 组件名和属性可以写在一行,前提是不超过单行最大字符限制
// bad
// good
```
- 1.5.3 `mandatory` 标签的属性有多行时,结束标签需另起一行。eslint: [react/jsx-closing-bracket-location](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md)
```jsx static
// bad
// good
```
- 1.5.4 `mandatory` 禁止在有子节点的组件或 DOM 元素中使用 dangerouslySetInnerHTML 属性。eslint: [react/no-danger-with-children](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger-with-children.md)
```jsx static
// bad
Children
Children
// good
Children
Children
```
- 1.5.5 `mandatory` HTML 自闭标签不能有子节点。eslint: [react/void-dom-elements-no-children](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/void-dom-elements-no-children.md)
HTML 自闭标签,比如 img,br,hr,被统称为空 DOM 元素,不能给他们定义子节点。
```jsx static
// bad
Children
// good
Children
```
- 1.5.6 `recommended` 不要使用危险属性。eslint: [react/no-danger](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md)
React中的危险属性是指那些已知会引起应用程序漏洞的属性。这些属性命名为 `dangerouslyXyz` 已经清楚地表明它们是危险的,应该尽量避免使用。[详细文档](https://facebook.github.io/react/tips/dangerously-set-inner-html.html)
```jsx static
// bad
;
// good
;
}
MyStatelessComponent.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
};
// good
function MyStatelessComponent({ foo, bar }) {
return
{foo}{bar}
;
}
MyStatelessComponent.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
};
MyStatelessComponent.defaultProps = {
bar: '',
};
```
- 2.3.8 `mandatory` 如果属性有 isRequired 类型检查,不要在 defaultProps 内对其赋值。eslint: [react/default-props-match-prop-types](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/default-props-match-prop-types.md)
propTypes 类型检查发生在 defaultProps 解析之后,如果在 defaultProps 赋值,isRequired 类型检查没有实际意义。
```jsx static
// bad
MyStatelessComponent.propTypes = {
foo: React.PropTypes.string.isRequired,
bar: React.PropTypes.string
};
MyStatelessComponent.defaultProps = {
foo: "foo"
};
// good
MyStatelessComponent.propTypes = {
foo: React.PropTypes.string.isRequired,
bar: React.PropTypes.string
};
MyStatelessComponent.defaultProps = {
bar: 'some default'
};
```
- 2.3.9 `recommended` 不要用数组的索引值作为 map 生成元素的 key。eslint: [react/no-array-index-key](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md)
为什么?React 使用 key 来标识哪些项已更改,已添加或已删除, [key 应该始终稳定](https://reactjs.org/docs/lists-and-keys.html#keys)。使用不稳定的 ID 是一种[反模式](https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318),因为它不能唯一标识元素。如果数组重新排序或将元素添加到数组的开头,可能会更改索引导致不必要的渲染,对性能产生负面影响。
如果数组的顺序可能发生变化,我们不建议使用索引值作为 key。
```jsx static
// bad
{todos.map((todo, index) =>
)}
// good
{todos.map(todo => (
))}
```
- 2.3.10 `mandatory` 禁止将 children 作为属性名。eslint: [react/no-children-prop](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md)
使用 JSX 时,`children` 应嵌套在开始和结束标签之间。不使用JSX时,应将 `children` 作为附加参数传递给 `React.createElement`。
```jsx static
// bad
} />
React.createElement("div", { children: 'Children' })
// good
Children
ChildrenChild 1Child 2
React.createElement("div", {}, 'Children')
React.createElement("div", 'Child 1', 'Child 2')
```
- 2.3.11 `mandatory` 不要声明重复的属性名。eslint: [react/jsx-no-duplicate-props](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md)
```jsx static
// bad
;
// good
;
```
- 2.3.12 `mandatory` style 的属性值必须是一个对象。eslint: [react/style-prop-object](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/style-prop-object.md)
```jsx static
// bad
const styles = true;
React.createElement("div", { style: "color: 'red'" });
React.createElement("div", { style: true });
React.createElement("Hello", { style: true });
const styles = true;
React.createElement("div", { style: styles });
// good
const styles = { color: "red" };
React.createElement("div", { style: { color: 'red' }});
React.createElement("Hello", { style: { color: 'red' }});
const styles = { height: '100px' };
React.createElement("div", { style: styles });
```
- 2.3.13 `recommended` 不要单独使用 target='_blank'。eslint: [react/jsx-no-target-blank](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-target-blank.md)
`target='_blank'` 常用于在新标签页打开。使用这个属性可能造成严重的安全问题。建议和 `rel='noreferrer noopener'` 一起使用。[详见](https://mathiasbynens.github.io/rel-noopener/)
```jsx static
// bad
const Hello =
const Hello =
// good
const Hello =
const Hello =
const Hello =
const Hello =
const Hello =
```
### 2.4 State
- 2.4.1 `mandatory` 不要在 setState 中使用 this.state。eslint: [react/no-access-state-in-setstate](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-access-state-in-setstate.md)
在 setState 中使用 this.state 可能导致错误,当两个 state 在同一个批处理中时,引用的是旧状态而不是新状态。
为避免这种情况,请在回调中使用 preState 作为第一个参数。
```jsx static
// bad
function increment() {
this.setState({ value: this.state.value + 1 });
}
// good
function increment() {
this.setState(prevState => ({ value: prevState.value + 1 }));
}
```
bad case 中假设 value 为1,有两个 setState 操作在同一个批处理中执行,实际执行的是:
```
setState({ value: 1 + 1 })
setState({ value: 1 + 1 })
```
good case 中 react 会以正确的更新后的状态调用参数。实际执行的是:
```
setState({ value: 1 + 1 })
setState({ value: 2 + 1 })
```
- 2.4.2 `mandatory` 声明的 state 必须被使用。eslint: [react/no-unused-state](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unused-state.md)
声明而未使用的 state 可能带来潜在的问题,也会给维护者造成困扰,应将它们删除。
```jsx static
// bad
class MyComponent extends React.Component {
state = { foo: 0 };
render() {
return ;
}
}
var UnusedGetInitialStateTest = createReactClass({
getInitialState: function() {
return { foo: 0 };
},
render: function() {
return ;
}
})
// good
class MyComponent extends React.Component {
state = { foo: 0 };
render() {
return ;
}
}
var UnusedGetInitialStateTest = createReactClass({
getInitialState: function() {
return { foo: 0 };
},
render: function() {
return ;
}
})
```
### 2.5 Refs
- 2.5.1 `mandatory` 使用 ref 回调函数或 React.createRef(),不要使用字符串。eslint: [react/no-string-refs](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md)
```jsx static
// bad - 使用字符串
class MyComponent extends React.Component {
componentDidMount() {
this.refs.inputRef.focus();
}
render() {
return ;
}
}
// good - 使用回调函数
class MyComponent extends React.Component {
componentDidMount() {
this.inputRef.focus();
}
render() {
return { this.inputRef = ele; }} />;
}
}
// good - 使用 React.createRef(),React V16 后版本支持
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
componentDidMount() {
this.inputRef.current.focus();
}
render() {
return ;
}
}
```
### 2.6 顺序
- 2.6.1 `referenced` 组件方法的排序规则。eslint: [react/sort-comp](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md)
React 组件内有声明周期方法、事件处理方法、render 方法等几类方法,指定这些方法按固定的顺序排序可以增强代码的一致性,方便查找和阅读。
我们推荐的方法排序如下:
- 可选的 `static` 方法
- `constructor`
- `getChildContext`
- `componentWillMount`
- `componentDidMount`
- `componentWillReceiveProps`
- `shouldComponentUpdate`
- `componentWillUpdate`
- `componentDidUpdate`
- `componentWillUnmount`
- *clickHandlers 或 eventHandlers* 比如 `onClickSubmit()` 或 `onChangeDescription()`
- *`render` 的 getter 方法* 比如 `getSelectReason()` 或 `getFooterContent()`
- *可选的 render 方法* 比如 `renderNavigation()` 或 `renderProfilePicture()`
- `render`
### 2.7 Mixins
- 2.7.1 `mandatory` 不要使用 mixins。
Mixins 引入了隐式依赖,可能导致命名冲突,并导致滚雪球式的复杂度。大多数使用 mixin 的场景都可以通过组件、高阶组件或工具模块以更好的方式完成。
## 3 命名
- 3.1 `mandatory` 文件扩展名: 使用 .jsx、.tsx、.js 或 .ts 作为 React 组件的文件扩展名。eslint: [react/jsx-filename-extension](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md)
- 3.2 `mandatory` 引用名:使用大驼峰风格命名引用的组件,使用小驼峰风格命名引用组件的实例。eslint: [react/jsx-pascal-case](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md)
```jsx static
// bad
import reservationCard from './reservation-card';
// good
import ReservationCard from './reservation-card';
// bad
const ReservationItem = ;
// good
const reservationItem = ;
```
- 3.3 `recommended` 高阶组件命名:将高阶组件名和传入组件名组合作为 displayName。
例如,高阶组件 `withFoo()` ,当传入组件 `Bar` 时,应该产生一个组件,应使用 withFoo(Bar) 作为生成组件的 displayName。
组件的 `displayName` 可被开发者工具和报错信息使用,这种组合的命名方式能清晰地表达高阶组件和被包裹组件的关系。
```jsx static
// bad
export default function withFoo(WrappedComponent) {
return function WithFoo(props) {
return ;
}
}
// good
export default function withFoo(WrappedComponent) {
function WithFoo(props) {
return ;
}
const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component';
WithFoo.displayName = `withFoo(${wrappedComponentName})`;
return WithFoo;
}
```
## 4 Hooks
- 4.1 `mandatory` 只在最顶层调用 Hooks,不要在循环、条件和嵌套函数中调用 Hooks。eslint: [rules of Hooks - only call Hooks at the top level](https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level)
```jsx static
// bad - call Hooks inside conditions
function ComponentWithConditionalHook() {
if (cond) {
useConditionalHook();
}
}
// bad - call Hooks inside loops
function ComponentWithHookInsideLoop() {
while (cond) {
useHookInsideLoop();
}
}
// bad - call Hooks inside callback
function ComponentWithHookInsideCallback() {
useEffect(() => {
useHookInsideCallback();
});
}
// good
function ComponentWithHook() {
useHook();
}
```
- 4.2 `mandatory` Hooks 命名必须以 `use` 开头,小驼峰形式
```jsx static
// bad
const customHook = () => {}
// good
const useCustomHook = () => {}
```
- 4.3 `mandatory` 只在 React 函数组件和自定义 Hooks 中调用 Hooks,不能在普通的 JavaScript 函数中调用 Hooks。eslint: [rules of Hooks - only call Hooks from React functions](https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions)
``` jsx
// bad - call Hooks inside class componennt
class ClassComponentWithHook extends React.Component {
render() {
React.useState();
}
}
// bad - call Hooks inside normal function
function normalFunctionWithHook() {
useHookInsideNormalFunction();
}
// good - call Hooks inside function component
function ComponentWithHook() {
useHook();
}
// good - call Hooks inside custom Hooks
function useHookWithHook() {
useHook();
}
```
- 4.4 `recommended` `useEffect` 及[类似 Hooks ](https://github.com/facebook/react/blob/3c1a7ac87c5b4903aa0de02d11bd9ec2590ad598/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L1518)需要声明所有依赖。eslint: [exhaustive-deps](https://github.com/facebook/react/issues/14920)
此规则在某些场景下可能过于严格,并且 ESLint autofix 可能会造成一些[问题](https://github.com/facebook/react/issues/16313),因此需注意:
- 升级 `eslint-plugin-react-hooks` 到 2.4.0 版本及以上,因为 [2.4.0 版本后该规则的 autofix 被默认禁用](https://github.com/facebook/react/blob/master/packages/eslint-plugin-react-hooks/CHANGELOG.md#240)
- 如果某些场景下此规则确实不适用,可以通过 ESLint 行注释手动禁用此规则,在行尾添加: `// eslint-disable-line react-hooks/exhaustive-deps`
```jsx static
// bad
function MyComponent() {
const local = {};
useEffect(() => {
console.log(local);
}, []);
}
// good
function MyComponent() {
const local = {};
useEffect(() => {
console.log(local);
}, [local]);
}
```
## 5 无障碍
[无障碍丰富互联网应用规范(WAI-ARIA,简称 ARIA)](https://www.w3.org/TR/wai-aria/)是 W3C 发布的技术规范,定义了一组可用于元素的 HTML 特性,作为对 HTML 语义化的补充,让残障人士能更加便利的访问 Web 内容和使用 Web 应用。
本章节会涉及到一些 WAI-ARIA 规范中的术语:
- 角色(role):定义了元素的种类。如 `role="button"` 告诉屏幕阅读器这是一个按钮元素。
- 属性(property):通过给元素定义一些属性,让他们具备更多的语义。例如 `aria-required="true"` 意味着该元素在表单上是必填的。
- 状态(state):用于表达元素当前的条件的特殊属性,例如 `aria-disabled="true"`,屏幕阅读器就会禁止编辑这个表单元素。状态和属性的差异之处就是:属性在应用的生命周期中不会改变,而状态可以,通常我们用编程的方法改变它,例如 JavaScript。
[这篇文档](https://developer.mozilla.org/zh-CN/docs/Learn/Accessibility/WAI-ARIA_basics)对 WAI-ARIA 规范的内容和使用做了初步介绍。
- 5.1 `recommended` img 标签应包含 alt 属性。eslint: [jsx-a11y/alt-text](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/alt-text.md)
如果图片无需被无障碍阅读器识别(如作为 button 的 icon 使用),你可以将 `alt` 属性写为空字符串
```jsx static
// bad
// good
// good - 图片无需被无障碍阅读器识别时
```
- 5.2 `recommended` img 标签的 alt 属性不要使用 "image","photo","picture" 之类的关键词。eslint: [jsx-a11y/img-redundant-alt](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-redundant-alt.md)
屏幕阅读器已会将 `img` 元素识别成图片,再在 alt 中包含这类关键词没有意义。
```jsx static
// bad
// good
```
- 5.3 `recommended` 锚元素(即 `` 元素)必须含有内容,且内容必须对屏幕阅读器可见(这里指内容不能通过设置 `aria-hidden` 属性隐藏)。eslint: [jsx-a11y/anchor-has-content](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-has-content.md)
```jsx static
// bad - empty content
// bad - content not accessible to screen readers
// good
Anchor Content!
```
- 5.4 `recommended` 禁止使用无效的 ARIA 属性,只能使用列在 [WAI-ARIA States and Properties spec](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties) 中的 `aria-*` 属性。eslint: [jsx-a11y/aria-props](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-props.md)
```jsx static
// bad - Labeled using incorrectly spelled aria-labeledby