合理书写React和JSX 该规范基于目前流行的JavaScript标准.尽管一些习惯要视情况来允许或禁止(如async/await或者静态的类字段).现阶段,任何超过stage 3的写法均不在此规范中且不推荐使用.
- 基本规则
- 类(Class) vs
React.createClass
vs 无状态(stateless) - 混入(Mixin)
- 命名
- 声明
- 对齐
- 引号
- 空格
- 属性(Props)
- 引用(Refs)
- 括号
- 标签
- 方法
- 顺序
isMounted
- 每个文件只包含一个React组件.
- 但是多个无状态组件可以放到一个文件中. eslint:
react/no-multi-comp
.
- 但是多个无状态组件可以放到一个文件中. eslint:
- 使用JSX语法.
- 除非是在非JSX文件中初始化应用,否则不要使用
React.createElement
.
-
如果组件有内部状态或引用, 建议使用
class extends React.Component
而不是React.createClass
. eslint:react/prefer-es6-class
react/prefer-stateless-function
// 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>; } }
如果没有状态或引用, 建议使用普通函数 (非箭头函数) 而不是类(class):
// 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>; }
- 不要使用mixin
原因: Mixin引入了隐式的依赖,导致命名冲突并且复杂度会滚雪球式的增加.多数mixin的情形都可以通过组件,高阶组件或工具模块等更好的方式实现.
-
文件后缀: React组件使用
.jsx
后缀. eslint:react/jsx-filename-extension
-
文件名: 文件名使用帕斯卡风格. 如
ReservationCard.jsx
. -
引用命名: React组件使用帕斯卡风格,组件实例使用驼峰风格. eslint:
react/jsx-pascal-case
// bad import reservationCard from './ReservationCard'; // good import ReservationCard from './ReservationCard'; // bad const ReservationItem = <ReservationCard />; // good const reservationItem = <ReservationCard />;
-
组件命名: 使用文件名作为组件名字. 例如,
ReservationCard.jsx
应该包含名为ReservationCard
的引用. 然而对于文件夹中的根组件, 使用index.jsx
作为文件名,使用文件夹的名字作为组件的名字:// bad import Footer from './Footer/Footer'; // bad import Footer from './Footer/index'; // good import Footer from './Footer';
-
高阶组件命名: 对于生成的组件使用高阶组件的名字和传入组件的名字的组合作为
displayName
. 例如高阶组件withFoo()
, 当传入组件Bar
时应该生成叫withFoo(Bar)
作为displayName
的组件.
原因? 组件的
displayName
名字能够用于开发工具或者错误信息, 有一个能够明显表达这种关系的值可以帮助我们理解发生了什么.
```jsx
// 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
这样的属性只代表具体的事情.为你的应用去改变这种API会使代码难以阅读和维护,可能会产生bug.
```jsx
// 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:
react/jsx-closing-bracket-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" > <Quux /> </Foo> // bad {showButton && <Button /> } // bad { showButton && <Button /> } // good {showButton && ( <Button /> )} // good {showButton && <Button />}
- JSX属性要使用双引号 (
"
), 但是其他JS部分要使用单引号. eslint:jsx-quotes
原因? 一般HTML属性也是使用双引号而不是单引号,所以JSX属性同样适用.
```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
属性. 如果图片是presentational(?不明),alt
可以为空字符串或者<img>
要有role="presentation"
. eslint:jsx-a11y/img-has-alt
// 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文本中包含这些信息.
```jsx
// bad
<img src="hello.jpg" alt="Picture of me waving hello" />
// good
<img src="hello.jpg" alt="Me waving hello" />
```
-
使用合法的非抽象的 ARIA roles. 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 />
- 避免使用数组的索引作为
key
属性值, 建议使用稳定的ID. eslint:react/no-array-index-key
原因? 不使用稳定的ID会对性能产生副作用并且组件状态会出问题 是一种反模式.
如果元素顺序可能改变的话我们不建议使用索引来作为key.
// 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属性.
例外:
- 向下代理属性并且提升propType的HOC
function HOC(WrappedComponent) {
return class Proxy extends React.Component {
Proxy.propTypes = {
text: PropTypes.string,
isLoading: PropTypes.bool
};
render() {
return <WrappedComponent {...this.props} />
}
}
}
- 对于已知且确定的属性来扩展对象. 这对于用MoCha的beforeEach构造方法来测试React组件会非常有用.
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} />
}
-
要使用回调函数作为引用. 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方法,在构造函数中绑定事件处理函数. eslint:
react/jsx-no-bind
原因:每次进行render调用
bind
时都会创建新的函数.
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组件的内部方法不要使用下划线前缀.
原因:下划线前缀有时被其他语言用来表示私有性.但是JavaScript不一样,它并不支持私有性(priviate),所有的都是公开的(public). 不管你怎么想, 属性添加下划线前缀并不会使其真正私有化, 任何属性 (无论是否有下划线前缀) 都应被视为公开的. 参考#1024和#490更多的讨论.
// bad
React.createClass({
_onClickSubmit() {
// do stuff
},
// other stuff
});
// good
class extends React.Component {
onClickSubmit() {
// do stuff
}
// other stuff
}
- 确保在
render
方法中返回值. eslint:require-render-return
// bad
render() {
(<div />);
}
// good
render() {
return (<div />);
}
class extends React.Component
的顺序:
- 可选的
static
方法 constructor
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
- 事件处理函数 例如
onClickSubmit()
或onChangeDescription()
render
的getter方法 如getSelectReason()
orgetFooterContent()
- 可选的render方法 如
renderNavigation()
orrenderProfilePicture()
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
的顺序: eslint:react/sort-comp
displayName
propTypes
contextTypes
childContextTypes
mixins
statics
defaultProps
getDefaultProps
getInitialState
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
- 事件处理函数 如
onClickSubmit()
或onChangeDescription()
render
的getter方法 如getSelectReason()
或getFooterContent()
- 可选的render方法 如
renderNavigation()
或renderProfilePicture()
render
- 不要使用
isMounted
. eslint:react/no-is-mounted
原因:
isMounted
是反模式, 当使用ES6的时候不可用,正逐渐被官方废弃.
I
其他语言的JSX/React编码规范翻译:
Chinese (Simplified): JasonBoy/javascript
Chinese (Traditional): jigsawye/javascript
Español: agrcrobles/javascript
Japanese: mitsuruog/javascript-style-guide
Korean: apple77y/javascript
Polish: pietraszekl/javascript
Portuguese: ronal2do/javascript
Russian: leonidlebedev/javascript-airbnb
Thai: lvarayut/javascript-style-guide
Ukrainian: ivanzusko/javascript