React 和 JSX 的最佳实践
这个指南大部分基于现在在JavaScript中流行的标准,尽管有些约定(如: async/await 或 class 的 static 字段)根据具体情况也会被引入或者被禁止。当前这个指南不包括也不推荐任何 ECMAScript stage-3(第三阶段提案) 之前的内容。
react/no-multi-comp
.React.createElement
, 除非你从一个非 JSX 文件中初始化 app。react/forbid-prop-types
只有在明确指定了数组和对象中包含的内容,使用 arrayOf
、 objectOf
或 shape
时才允许使用数组和对象。React.createClass
vs stateless如果你要用 state refs, 最好用 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>;
}
}
如果你没有使用 state、 refs ,最好用正常函数(不是箭头函数)而不是 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>;
}
Why? mixins 会引入一些隐含依赖,导致命名冲突,会导致滚雪球式的复杂度。大多数情况下,mixins 都可以通过组件,高阶组件 HOC或者工具模块更好的实现。
.jsx
作为组件扩展名。eslint: 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';
高阶组件HOC
命名: 用高阶组件名和传入的组件名组合作为生成的组件的 displayName
。 举个例子,一个高阶组件 withFoo()
, 当传入一个组件 Bar
应该生成一个新的组件,他的 displayName
属性是 withFoo(Bar)
。
Why? 组件的
displayName
可以用于开发者工具或者错误信息中,同时还有一个值可以清晰的表达这种组件关系,这可以帮助人们理解到底发生了什么
// 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;
}
Props 命名: 避免用 DOM 组件的属性名表达不同的意义
Why? 人们期望
style
、className
这种属性代表一个明确的意义。 为应用程序的一个子集改变此API会使代码的可读性降低,维护性降低,并可能导致错误。
// 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
react/jsx-closing-tag-location
// bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
// good
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// 如果能放在一行,也可以用单行表示
<Foo bar="bar" />
// Foo 里面的标签正常缩进
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Quux />
</Foo>
// bad
{showButton &&
<Button />
}
// bad
{
showButton &&
<Button />
}
// good
{showButton && (
<Button />
)}
// good
{showButton && <Button />}
在 JSX 属性中用双引号("
),但是在js里用单引号('
)。eslint: jsx-quotes
Why? 正常的 HTML 属性也通常使用双引号而不是单引号,所以 JSX 属性也使用这个约定。
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style= />
// good
<Foo style= />
在自闭合标签内空一格。 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} />
props 用小驼峰
// bad
<Foo
UserName="hello"
phone_number={12345678}
/>
// good
<Foo
userName="hello"
phoneNumber={12345678}
/>
如果 prop 的值是 true 可以忽略这个值,直接写 prop 名就可以。 eslint: react/jsx-boolean-value
// bad
<Foo
hidden={true}
/>
// good
<Foo
hidden
/>
// good
<Foo hidden />
<img>
标签通常会设置 alt
属性。如果图片是表现型的, alt
可以是空字符串或者 <img>
必须有 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
Why? 因为屏幕阅读器已经将
img
发音为图片了,所以这个信息就不需要出现在 alt 文本里了。
// 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 - 不是一个 ARIA role
<div role="datepicker" />
// bad - 抽象的 ARIA role
<div role="range" />
// good
<div role="button" />
不要在元素上用 accessKey
。 eslint: jsx-a11y/no-access-key
Why? 使用屏幕阅读器和键盘的人使用的键盘快捷键和键盘命令之间的不一致使得可访问性变得复杂。
// bad
<div accessKey="h" />
// good
<div />
key
属性,推荐用稳定的 ID。 eslint: react/no-array-index-key
Why? 不使用稳定杆的 ID is an anti-pattern 会对组件性能产生消极影响,并且组件状态容易出现问题。 如果数组元素可能会发生变化,我们不推荐使用下标作为key。
// bad
{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}
// good
{todos.map(todo => (
<Todo
{...todo}
key={todo.id}
/>
))}
Why? propTypes 是一个文档形式,同时提供默认属性意味着使用者不需要假定那么多值。另外,这也意味着你的代码可以忽略类型检查。
// 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,
};
{...props}
Why? 除非你更喜欢把不需要的props属性传入组件。而且对于 v15.6.1 及更早以前的 React, 你只能给DOM元素传非HTML属性的props。
例外:
function HOC(WrappedComponent) {
return class Proxy extends React.Component {
Proxy.propTypes = {
text: PropTypes.string,
isLoading: PropTypes.bool
};
render() {
return <WrappedComponent {...this.props} />
}
}
}
export default function Foo {
const props = {
text: '',
isPublished: false
}
return (<div {...props} />);
}
使用说明: 尽可能过滤出不需要的属性。同时用prop-type-exact去帮助避免bug。
// bad
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...this.props} />
}
// good
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...relevantProps} />
}
推荐用 ref callback 函数。 eslint: react/no-string-refs
// bad
<Foo
ref="myRef"
/>
// good
<Foo
ref={(ref) => { this.myRef = ref; }}
/>
当 JSX 标签有多行时,用圆括号包起来。 eslint: react/jsx-wrap-multilines
// bad
render() {
return <MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>;
}
// good
render() {
return (
<MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
// good, 单行可以直接写
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
当没有子元素时,最好用自闭合标签。 eslint: react/self-closing-comp
// bad
<Foo variant="stuff"></Foo>
// good
<Foo variant="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>
);
}
在构造函数里绑定事件处理函数。 eslint: react/jsx-no-bind
Why? render 函数中的绑定调用在每次 render 的时候都会创建一个新的函数。
// 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} />;
}
}
Why? 下划线前缀有时候在其他语言里被用于表示私有。但是 JavaScript 原生并不支持私有,所有东西都是公有的。尽管在你的意图里,对你的属性添加下划线前缀不是真的是他变成私有属性,而且任何属性(不论是不是下划线前缀)都被认为是公有的。详细讨论见问题#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 />);
}
class extends React.Component
内部属性的顺序:static
方法constructor
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
onClickSubmit()
、 onChangeDescription()
render
如: getSelectReason()
、 getFooterContent()
renderNavigation()
、 renderProfilePicture()
render
如何定义 propTypes
、 defaultProps
、 contextTypes
等…
import React from 'react';
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
内部属性排序: eslint: react/sort-comp
displayName
propTypes
contextTypes
childContextTypes
mixins
statics
defaultProps
getDefaultProps
getInitialState
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
onClickSubmit()
、 onChangeDescription()
render
如: getSelectReason()
、 getFooterContent()
renderNavigation()
、 renderProfilePicture()
render
isMounted
isMounted
。 eslint: react/no-is-mounted
Why? [
isMounted
是反模式][anti-pattern], 这个在 ES6 class 里不允许的,而且即将被官方废弃。
JSX/REACT 风格指南的其他语言翻译版