Airbnb React 编码规范
内容目录
- 基本规范
- Class、React.createClass 与 stateless
- Mixins(混入)
- 命名
- 声明模块
- 代码对齐
- 单引号还是双引号
- 空格
- 属性
- Refs 引用
- 括号
- 标签
- 函数/方法
- 模块生命周期
- isMounted
Basic Rules 基本规范
- 每个文件只写一个模块.
- 但多个无状态模块 (opens in a new tab)可以放在单个文件中. eslint:
react/no-multi-comp
(opens in a new tab). - 推荐使用 JSX 语法.
- 不要使用
React.createElement
,除非在从一个非 JSX 的文件中初始化 app. - 只有在使用
arrayOf
,objectOf
, 或shape
明确指出arrays
和objects
所包含的内容时,react/forbid-prop-types
(opens in a new tab) 才会允许这个属性 (props).
创建模块
Class vs React.createClass vs stateless
- 如果你的模块有内部状态或者是
refs
, 推荐使用class extends React.Component
而不是React.createClass
. eslint:react/prefer-es6-class
(opens in a new tab)react/prefer-stateless-function
(opens in a new tab)
// bad
const Listing = React.createClass({
// ...
render() {
return <div>{this.state.hello}</div>;
}
});
// good
class Listing extends React.Component {
// ...
render() {
return <div>{this.state.hello}</div>;
}
}
如果你的模块没有状态或是没有引用refs
, 推荐使用普通函数(非箭头函数)而不是类:
// bad
class Listing extends React.Component {
render() {
return <div>{this.props.hello}</div>;
}
}
// bad (relying on function name inference is discouraged)
const Listing = ({ hello }) => (
<div>{hello}</div>
);
// good
function Listing({ hello }) {
return <div>{hello}</div>;
}
Mixins
为什么? Mixins 会增加隐式的依赖,导致命名冲突,并且会以雪球式增加复杂度。在大多数情况下Mixins可以被更好的方法替代,如:组件化,高阶组件,工具模块等。
Naming 命名
- 扩展名: React模块使用
.jsx
扩展名.
-
文件名: 文件名使用帕斯卡命名. 如,
ReservationCard.jsx
. -
引用命名: React模块名使用帕斯卡命名,实例使用骆驼式命名. eslint:
react/jsx-pascal-case
(opens in a new tab)
// bad
import reservationCard from './ReservationCard';
// good
import ReservationCard from './ReservationCard';
// bad
const ReservationItem = <ReservationCard />;
// good
const reservationItem = <ReservationCard />;
- 模块命名: 模块使用当前文件名一样的名称. 比如
ReservationCard.jsx
应该包含名为ReservationCard
的模块. 但是,如果整个文件夹是一个模块,使用index.js
作为入口文件,然后直接使用index.js
或者文件夹名作为模块的名称:
// bad
import Footer from './Footer/Footer';
// bad
import Footer from './Footer/index';
// good
import Footer from './Footer';
- 高阶模块命名: 对于生成一个新的模块,其中的模块名
displayName
应该为高阶模块名和传入模块名的组合. 例如, 高阶模块withFoo()
, 当传入一个Bar
模块的时候, 生成的模块名displayName
应为withFoo(Bar)
.
为什么?一个模块的
displayName
可能会在开发者工具或者错误信息中使用到,因此有一个能清楚的表达这层关系的值能帮助我们更好地理解模块发生了什么,更好地进行Debug。
// bad
export default function withFoo(WrappedComponent) {
return function WithFoo(props) {
return <WrappedComponent {...props} foo />;
}
}
// good
export default function withFoo(WrappedComponent) {
function WithFoo(props) {
return <WrappedComponent {...props} foo />;
}
const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component';
WithFoo.displayName = `withFoo(${wrappedComponentName})`;
return WithFoo;
}
- 属性命名: 避免使用DOM相关的属性来用作其他的用途。
为什么?对于
style
和className
这样的属性名,我们都会默认它们代表一些特殊的含义,如元素的样式,CSS class的名称。在你的应用中使用这些属性来表示其他的含义会使你的代码更难阅读,更难维护,并且可能会引起bug。
// bad
<MyComponent style="fancy" />
// good
<MyComponent variant="fancy" />
Declaration 声明模块
- 不要使用
displayName
来命名React模块,而是使用引用来命名模块, 如 class 名称.
// bad
export default React.createClass({
displayName: 'ReservationCard',
// stuff goes here
});
// good
export default class ReservationCard extends React.Component {
}
Alignment 代码对齐
- 遵循以下的JSX语法缩进/格式. eslint:
react/jsx-closing-bracket-location
(opens in a new tab)react/jsx-closing-tag-location
(opens in a new tab)
// bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
// good
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// if props fit in one line then keep it on the same line
<Foo bar="bar" />
// children get indented normally
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Quux />
</Foo>
// bad
{showButton &&
<Button />
}
// bad
{
showButton &&
<Button />
}
// good
{showButton && (
<Button />
)}
// good
{showButton && <Button />}
// good
{someReallyLongConditional
&& anotherLongConditional
&& (
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
)
}
// good
{someConditional ? (
<Foo />
) : (
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
)}
Quotes 单引号还是双引号
- 对于JSX属性值总是使用双引号(
"
), 其他均使用单引号('
). eslint:jsx-quotes
(opens in a new tab)
为什么? HTML属性也是用双引号, 因此JSX的属性也遵循此约定.
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />
Spacing 空格
- 总是在自动关闭的标签前加一个空格,正常情况下也不需要换行. eslint:
no-multi-spaces
(opens in a new tab),react/jsx-tag-spacing
(opens in a new tab)
// bad
<Foo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Foo />
- 不要在JSX
{}
引用括号里两边加空格. eslint:react/jsx-curly-spacing
(opens in a new tab)
// bad
<Foo bar={ baz } />
// good
<Foo bar={baz} />
Props 属性
- JSX 属性名使用小骆驼拼写法
camelCase
,如果属性名是一个 React 组件名,则使用大骆驼拼写法PascalCase
// bad
<Foo
UserName="hello"
phone_number={12345678}
/>
// good
<Foo
userName="hello"
phoneNumber={12345678}
Component={SomeComponent}
/>
- 如果属性值为
true
, 可以直接省略. eslint:react/jsx-boolean-value
(opens in a new tab)
// bad
<Foo
hidden={true}
/>
// good
<Foo
hidden
/>
// good
<Foo hidden />
<img>
标签总是添加alt
属性. 如果图片以presentation(感觉是以类似PPT方式显示?)方式显示,alt
可为空, 或者<img>
要包含role="presentation"
. eslint:jsx-a11y/alt-text
(opens in a new tab)
// bad
<img src="hello.jpg" />
// good
<img src="hello.jpg" alt="Me waving hello" />
// good
<img src="hello.jpg" alt="" />
// good
<img src="hello.jpg" role="presentation" />
- 不要在
alt
值里使用如 "image", "photo", or "picture"包括图片含义这样的词, 中文也一样. eslint:jsx-a11y/img-redundant-alt
(opens in a new tab)
为什么? 屏幕助读器已经把
img
标签标注为图片了, 所以没有必要再在alt
里说明了.
// bad
<img src="hello.jpg" alt="Picture of me waving hello" />
// good
<img src="hello.jpg" alt="Me waving hello" />
- 使用有效正确的 aria
role
属性值 ARIA roles (opens in a new tab). eslint:jsx-a11y/aria-role
(opens in a new tab)
// bad - not an ARIA role
<div role="datepicker" />
// bad - abstract ARIA role
<div role="range" />
// good
<div role="button" />
- 不要在标签上使用
accessKey
属性. eslint:jsx-a11y/no-access-key
(opens in a new tab)
为什么? 屏幕助读器在键盘快捷键与键盘命令时造成的不统一性会导致阅读性更加复杂.
// bad
<div accessKey="h" />
// good
<div />
- 避免使用数组的 index 来作为
key
属性的值。
译者注:key 属性指名称为
key
的属性,即props.key
应当使用稳定不变的 ID。(使用不稳定的 ID 是一个反面模式 (opens in a new tab),会降低性能、造成组件状态出错) 。特别是当元素的顺序可能改变的情况下,不应使用数组的 index 作为 key
.
译者注:反面模式 (Anti-Pattern),指低效或是有待优化的软件设计模式。
// bad
{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}
// good
{todos.map(todo => (
<Todo
{...todo}
key={todo.id}
/>
))}
- 对于所有非必须的属性,总是手动去定义
defaultProps
属性.
为什么? propTypes 可以作为模块的文档说明, 并且声明 defaultProps 的话意味着阅读代码的人不需要去假设一些默认值。更重要的是, 显示的声明默认属性可以让你的模块跳过属性类型的检查.
// bad
function SFC({ foo, bar, children }) {
return <div>{foo}{bar}{children}</div>;
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
// good
function SFC({ foo, bar, children }) {
return <div>{foo}{bar}{children}</div>;
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
SFC.defaultProps = {
bar: '',
children: null,
};
- 尽可能少地使用扩展运算符
为什么? 除非你很想传递一些不必要的属性。对于React v15.6.1和更早的版本,你可以给DOM传递一些无效的HTML属性 (opens in a new tab)
例外情况:
- 使用了变量提升的高阶组件
function HOC(WrappedComponent) {
return class Proxy extends React.Component {
Proxy.propTypes = {
text: PropTypes.string,
isLoading: PropTypes.bool
};
render() {
return <WrappedComponent {...this.props} />
}
}
}
- 只有在清楚明白扩展对象时才使用扩展运算符。这非常有用尤其是在使用Mocha测试组件的时候。
export default function Foo {
const props = {
text: '',
isPublished: false
}
return (<div {...props} />);
}
特别提醒:尽可能地筛选出不必要的属性。同时,使用prop-types-exact (opens in a new tab)来预防问题出现。
// bad
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...this.props} />
}
// good
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...relevantProps} />
}
Refs
- 总是在Refs里使用回调函数. eslint:
react/no-string-refs
(opens in a new tab)
// bad
<Foo
ref="myRef"
/>
// good
<Foo
ref={(ref) => { this.myRef = ref; }}
/>
Parentheses 括号
- 将多行的JSX标签写在
()
里. eslint:react/jsx-wrap-multilines
(opens in a new tab)
// bad
render() {
return <MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>;
}
// good
render() {
return (
<MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
// good, 单行可以不需要
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
Tags 标签
- 对于没有子元素的标签来说总是自己关闭标签. eslint:
react/self-closing-comp
(opens in a new tab)
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
- 如果模块有多行的属性, 关闭标签时新建一行. eslint:
react/jsx-closing-bracket-location
(opens in a new tab)
// bad
<Foo
bar="bar"
baz="baz" />
// good
<Foo
bar="bar"
baz="baz"
/>
Methods 函数
- 使用箭头函数来获取本地变量。这使得传递数据给事件处理器 (event handler) 很方便。不过,尤其是当传递给自定义的纯组件 (PureComponent) 时,要确保这些箭头函数对性能影响不是太大,因为它们会每次都会触发可能无意义的重新渲染。
function ItemList(props) {
return (
<ul>
{props.items.map((item, index) => (
<Item
key={item.key}
onClick={(event) => { doSomethingWith(event, item.name, index); }}
/>
))}
</ul>
);
}
- 当在
render()
里使用事件处理方法时,提前在构造函数里把this
绑定上去. eslint:react/jsx-no-bind
(opens in a new tab)
为什么? 在每次
render
过程中, 再调用bind
都会新建一个新的函数,浪费资源。在类成员变量 (class fields) 里不要使用箭头函数,因为箭头函数会造成它难以测试和调试,并会降低性能。从概念上讲,类成员变量存的应该是数据,而不是逻辑或方法。
// bad
class extends React.Component {
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv.bind(this)} />;
}
}
very bad
ss extends React.Component {
nClickDiv = () => {
// do stuff
ender() {
return <div onClick={this.onClickDiv} />
// good
class extends React.Component {
constructor(props) {
super(props);
this.onClickDiv = this.onClickDiv.bind(this);
}
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv} />;
}
}
- 在React模块中,不要给所谓的私有函数添加
_
前缀,本质上它并不是私有的.
为什么?
_
下划线前缀在某些语言中通常被用来表示私有变量或者函数。但是不像其他的一些语言,在JS中没有原生支持所谓的私有变量,所有的变量函数都是共有的。尽管你的意图是使它私有化,在之前加上下划线并不会使这些变量私有化,并且所有的属性(包括有下划线前缀及没有前缀的)都应该被视为是共有的。了解更多详情请查看Issue #1024 (opens in a new tab), 和 #490 (opens in a new tab) 。
// bad
React.createClass({
_onClickSubmit() {
// do stuff
},
// other stuff
});
// good
class extends React.Component {
onClickSubmit() {
// do stuff
}
// other stuff
}
- 在
render
方法中总是确保return
返回值. eslint:react/require-render-return
(opens in a new tab)
// bad
render() {
(<div />);
}
// good
render() {
return (<div />);
}
Ordering React 模块生命周期
class extends React.Component
的生命周期函数:
- 可选的
static
方法 constructor
构造函数getChildContext
获取子元素内容componentWillMount
模块渲染前componentDidMount
模块渲染后componentWillReceiveProps
模块将接受新的数据shouldComponentUpdate
判断模块需不需要重新渲染componentWillUpdate
上面的方法返回true
, 模块将重新渲染componentDidUpdate
模块渲染结束componentWillUnmount
模块将从DOM中清除, 做一些清理任务- 点击回调或者事件处理器 如
onClickSubmit()
或onChangeDescription()
render
里的 getter 方法 如getSelectReason()
或getFooterContent()
- 可选的 render 方法 如
renderNavigation()
或renderProfilePicture()
render
render() 方法
- 如何定义
propTypes
,defaultProps
,contextTypes
, 等等其他属性...
import React from 'docs/code-standard/01.代码规范/04.Airbnb React JSX 风格指南';
import PropTypes from 'prop-types';
const propTypes = {
id: PropTypes.number.isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string,
};
const defaultProps = {
text: 'Hello World',
};
class Link extends React.Component {
static methodsAreOk() {
return true;
}
render() {
return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>;
}
}
Link.propTypes = propTypes;
Link.defaultProps = defaultProps;
export default Link;
React.createClass
的生命周期函数,与使用class稍有不同: eslint:react/sort-comp
(opens in a new tab)
displayName
设定模块名称propTypes
设置属性的类型contextTypes
设置上下文类型childContextTypes
设置子元素上下文类型mixins
添加一些mixinsstatics
defaultProps
设置默认的属性值getDefaultProps
获取默认属性值getInitialState
或者初始状态getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
- clickHandlers or eventHandlers like
onClickSubmit()
oronChangeDescription()
- getter methods for
render
likegetSelectReason()
orgetFooterContent()
- Optional render methods like
renderNavigation()
orrenderProfilePicture()
render
isMounted
- 不要再使用
isMounted
. eslint:react/no-is-mounted
(opens in a new tab)
为什么?
isMounted
反人类设计模式:() (opens in a new tab), 在 ES6 classes 中无法使用, 官方将在未来的版本里删除此方法.