Awesome
React 编程规范(by Airbnb)
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->基本规则
- 每个文件只包含一个 React 组件
- 不过可以包含多个 Stateless 或 Pure 组件。 eslint 规则:react/no-multi-comp
- 使用
JSX
语法 - 除非是从一个非
JSX
文件中初始化 app,否则不要使用React.createElement
Class vs React.createClass
- 如果需要管理内部状态或
refs
,优先使用class extends React.Component
。eslint 规则:react/prefer-es6-class, react/prefer-stateless-function
// bad
const Listing = React.createClass({
render() {
return <div />;
}
});
// good
class Listing extends React.Component {
render() {
return <div />;
}
}
反之, 则优先使用普通函数(不建议使用箭头函数)。
// bad
class Listing extends React.Component {
render() {
return <div>{this.props.hello}</div>;
}
}
// bad
const Listing = ({ hello }) => (
<div>{hello}</div>
);
// good
function Listing({ hello }) {
return <div>{hello}</div>;
}
混淆
为什么? Mixins 会增加隐式的依赖,导致命名冲突,并且会以雪球式增加复杂度。在大多数情况下Mixins可以被更好的方法替代,如:组件化,高阶组件,工具模块等。
命名
- 扩展名: 使用
jsx
作为 React 组件的扩展名 - 文件名: 文件命名采用帕斯卡命名法,如:
ReservationCard.jsx
- 引用名: 组件引用采用帕斯卡命名法,其实例采用驼峰式命名法。eslint rules: react/jsx-pascal-case
// bad
const reservationCard = require('./ReservationCard');
// good
const ReservationCard = require('./ReservationCard');
// bad
const ReservationItem = <ReservationCard />;
// good
const reservationItem = <ReservationCard />;
- 组件命名: 使用文件名作为组件名。例如:
ReservationCard.jsx
组件的引用名应该是ReservationCard
。然而,对于一个目录的根组件,应该使用index.jsx
作为文件名,使用目录名作为组件名。
// bad
const Footer = require('./Footer/Footer.jsx')
// bad
const Footer = require('./Footer/index.jsx')
// good
const Footer = require('./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
这样的属性名会默认代表一些含义,在你的应用中使用这些属性来表示其他的含义会使你的代码更难阅读,更难维护,并且可能会引起bug。
// bad
<MyComponent style="fancy" />
// bad
<MyComponent className="fancy" />
// good
<MyComponent variant="fancy" />
声明
- 不要通过
displayName
来命名组件,通过引用来命名组件
// bad
export default React.createClass({
displayName: 'ReservationCard',
// stuff goes here
});
// good
export default class ReservationCard extends React.Component {
}
对齐
- 对于
JSX
语法,遵循下面的对齐风格。eslint rules: react/jsx-closing-bracket-location, react/jsx-closing-tag-location
// 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"
>
<Spazz />
</Foo>
引号
- 对于
JSX
使用双引号,对其它所有 JS 属性使用单引号。eslint:jsx-quotes
为什么?因为 JSX 属性不能包含被转移的引号,并且双引号使得如
"don't"
一样的连接词很容易被输入。常规的 HTML 属性也应该使用双引号而不是单引号,JSX 属性反映了这个约定。
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />
空格
- 在自闭和标签之前留一个空格。eslint: no-multi-spaces, react/jsx-tag-spacing
// bad
<Foo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Foo />
- 不要在JSX
{}
引用括号里两边加空格. eslint: react/jsx-curly-spacing
// bad
<Foo bar={ baz } />
// good
<Foo bar={baz} />
属性
- 属性名采用驼峰式命名法
// bad
<Foo
UserName="hello"
phone_number={12345678}
/>
// good
<Foo
userName="hello"
phoneNumber={12345678}
/>
- 属性值为
true
,可以忽略赋值。eslint: react/jsx-boolean-value
// bad
<Foo
hidden={true}
/>
// good
<Foo
hidden
/>
// good
<Foo hidden />
img
标签要添加alt
属性或者role="presentation"
。 eslint: jsx-a11y/alt-text
// 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" />
- 不要在
img
标签的alt
属性中使用 "image", "photo", 或 "picture" 一类的单词。eslint:jsx-a11y/img-redundant-alt
为什么? 屏幕助读器已经将 img 作为图片了,所以没必要再在 alt 属性中进行说明
// bad
<img src="hello.jpg" alt="Picture of me waving hello" />
// good
<img src="hello.jpg" alt="Me waving hello" />
- 只是用正确且具体的 ARIA 属性。eslint:jsx-a11y/aria-role
// 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
为什么?屏幕助读器在键盘快捷键与键盘命令时造成的不统一性会导致阅读性更加复杂。
// bad
<div accessKey="h" />
// good
<div />
- 避免使用数组的
index
来作为属性key
的值,推荐使用唯一ID(为什么?)
// 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属性
例外情况:
- 使用了变量提升的高阶组件
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能预防 bug。
//good
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...relevantProps} />
}
//bad
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...this.props} />
}
Refs
- 使用 ref callbacks。eslint:react/no-string-refs
// bad
<Foo
ref="myRef"
/>
// good
<Foo
ref={(ref) => { this.myRef = ref; }}
/>
括号
- 当组件跨行时,要用括号包裹 JSX 标签。eslint: react/wrap-multilines
/// bad
render() {
return <MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>;
}
// good
render() {
return (
<MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
// good, when single line
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
标签
- 没有子组件的父组件使用自闭和标签。eslint: react/self-closing-comp
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
- 如果组件有多行属性,闭合标签应写在新的一行上。eslint: react/jsx-closing-bracket-location
// bad
<Foo
bar="bar"
baz="baz" />
// good
<Foo
bar="bar"
baz="baz"
/>
方法
- 使用箭头函数来访问本地变量
function ItemList(props) {
return (
<ul>
{props.items.map((item, index) => (
<Item
key={item.key}
onClick={() => doSomethingWith(item.name, index)}
/>
))}
</ul>
);
}
- 在构造函数中绑定需要在
render
方法使用的事件处理函数(绑定this
)。eslint:react/jsx-no-bind
为什么?在组件每次
render
时, 每次 bind 调用都会创建新的函数
// bad
class extends React.Component {
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv.bind(this)} />;
}
}
// 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 组件的内置方法使用
underscore
前缀
为什么?
_
前缀在某些语言中通常被用来表示私有变量或者函数,但在原生 JavaScript 不支持所谓的私有变量,所有的变量函数都是共有的。在变量名之前加上下划线并不会使这些变量私有化,并且所有的属性都应该被视为是共有的。相关 issue:#1024 / #490 。
// bad
React.createClass({
_onClickSubmit() {
// do stuff
}
// other stuff
});
// good
class extends React.Component {
onClickSubmit() {
// do stuff
}
// other stuff
});
- 确保
render
方法中有返回值。eslint: react/require-render-return
// bad
render() {
(<div />);
}
// good
render() {
return (<div />);
}
顺序
- 继承 React.Component 的类的方法遵循下面的顺序
- constructor
- optional static methods
- getChildContext
- componentWillMount
- componentDidMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- componentDidUpdate
- componentWillUnmount
- clickHandlers or eventHandlers like onClickSubmit() or onChangeDescription()
- getter methods for render like getSelectReason() or getFooterContent()
- Optional render methods like renderNavigation() or renderProfilePicture()
- render
- 怎么定义 propTypes,defaultProps,contextTypes 等等...
import React, { PropTypes } from 'react';
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 时,方法顺序如下:
- displayName
- propTypes
- contextTypes
- childContextTypes
- mixins
- statics
- defaultProps
- getDefaultProps
- getInitialState
- getChildContext
- componentWillMount
- componentDidMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- componentDidUpdate
- componentWillUnmount
- clickHandlers or eventHandlers like onClickSubmit() or onChangeDescription()
- getter methods for render like getSelectReason() or getFooterContent()
- Optional render methods like renderNavigation() or renderProfilePicture()
- render
eslint: react/sort-comp
isMounted
- 不要再使用
isMounted
. eslint: react/no-is-mounted
为什么?isMounted 是一种反模式,在 ES6 classes 中不可用。官方未来将会删除改属性。·
Related Resource
·