一套对于 React 和 JSX 基本合理的规范
本指南是主要基于目前流行的 JavaScript 标准,因此一些惯例(如:async/await 或静态类字段)可能仍然包含其中或者被禁止。 目前,处在第 3 阶段的任何内容都不包含在本指南中,也不建议使用。
- 基本规则
- Class vs
React.createClass
vs stateless - Mixins
- 命名
- 声明模块
- 代码对齐
- 单引号和双引号
- 空格
- 属性
- Refs
- 括号
- 标签
- 方法
- 函数生命周期
isMounted
- 每个文件只存在一个 React 组件。
- 但是,每个文件允许存在多个 无状态或纯组件 。 eslint:
react/no-multi-comp
.
- 但是,每个文件允许存在多个 无状态或纯组件 。 eslint:
- 始终使用 JSX 语法。
- 除非你从一个非 JSX 文件中初始化你的应用,否则不要使用
React.createElement
。
-
如果你的组件有内部状态或者是
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>; } }
如果你的组件没有状态或者
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 能够被更好的方法替代,如:组件化,高阶组件,工具模块等。
-
扩展名: 使用
.jsx
作为 React 组件的扩展名。 -
文件名: 使用帕斯卡命名法给文件命名。 例如:
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
的组件中的时候,应该生成一个拥有displayName
为withFoo(Bar)
的组件。为什么? 一个组件的
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; }
-
属性命名: 避免使用 DOM 组件属性来用作其他用途。
为什么? 对于像
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 superLongParam="bar" anotherSuperLongParam="baz" > <Quux /> </Foo>
-
对于 JSX 属性总是使用双引号 (
"
), 对于其他的 JS 来说使用单引号 ('
)。 eslint:jsx-quotes
为什么? 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
属性。 如果图片是直观的,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
为什么? 屏幕阅读器已经把
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
为什么? 设置的键盘快捷键和使用显示器和键盘的人的键盘命令不一致会导致访问的复杂性。
// bad
<div accessKey="h" />
// good
<div />
- 避免使用数组的索引作为
key
属性的值,应该是唯一的 ID。 (why?)
// 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 .
例外:
- 代理高阶组件和 propTypes 变量提升。
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 来帮助避免错误。
// good
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...relevantProps} />
}
// bad
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...this.props} />
}
-
总是在 ref 中使用回调函数。 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> ); }
-
在构造器中为 render 函数绑定处理方法。 eslint:
react/jsx-no-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 组件的内部方法中使用下划线前缀。
为什么? 下划线前缀有时被用作其他语言的约定来表示隐私。 但是,与其他语言不同, 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
的 get 方法 像getSelectReason()
或者getFooterContent()
- 可选的 render 方法 像
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
的 get 方法 像getSelectReason()
或者getFooterContent()
- 可选的 render 方法 像
renderNavigation()
或者renderProfilePicture()
render
- 不要使用
isMounted
。 eslint:react/no-is-mounted
为什么?
isMounted
是一种反模式,在使用 ES6 类定义的时候不可用,并且正在被正式弃用。
**[⬆ 返回目录](#table-of-contents)**