React 风格指南
这个指南大部分基于现在在JavaScript中流行的标准,尽管有些约定(如: async/await 或 class 的 static 字段)根据具体情况也会被引入或者被禁止。当前这个指南不包括也不推荐任何 ECMAScript stage-3(第三阶段提案) 之前的内容。
基本规则
- 每个文件只包含一个 React 组件
- 然而,在一个文件里包含多个没有 state 或纯组件是允许的。 eslint:
react/no-multi-comp
.
- 然而,在一个文件里包含多个没有 state 或纯组件是允许的。 eslint:
- 经常用 JSX 语法。
- 不要用
React.createElement
, 除非你从一个非 JSX 文件中初始化 app。
React.createClass
vs stateless vs hook
Class vs 如果你要用 state refs, 最好用
class extends React.Component
而不是React.createClass
. eslint:react/prefer-es6-class
react/prefer-stateless-function
// badconst Listing = React.createClass({// ...render() {return <div>{this.state.hello}</div>;}});// goodclass Listing extends React.Component {// ...render() {return <div>{this.state.hello}</div>;}}如果你没有使用 state、 refs ,最好用正常函数(不是箭头函数)而不是 class:
// badclass 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>);// goodfunction Listing({ hello }) {return <div>{hello}</div>;}
命名规范
扩展名: 用
.tsx
作为组件扩展名。文件名: 用大驼峰作为文件名,如:
ReservationCard.tsx
。参数命名: React 组件用大驼峰,组件的实例用小驼峰。 eslint:
react/jsx-pascal-case
// badimport reservationCard from './ReservationCard';// goodimport ReservationCard from './ReservationCard';// badconst ReservationItem = <ReservationCard />;// goodconst reservationItem = <ReservationCard />;组件命名: 文件名作为组件名。例如:
ReservationCard.jsx
应该用ReservationCard
作为参数名。 然而,对于一个文件夹里的跟组件,应该用index.jsx
作为文件名,同时用文件夹名作为组件名// badimport Footer from './Footer/Footer';// badimport Footer from './Footer/index';// goodimport Footer from './Footer';高阶组件
HOC
命名: 用高阶组件名和传入的组件名组合作为生成的组件的displayName
。 举个例子,一个高阶组件withFoo()
, 当传入一个组件Bar
应该生成一个新的组件,他的displayName
属性是withFoo(Bar)
。Why? 组件的
displayName
可以用于开发者工具或者错误信息中,同时还有一个值可以清晰的表达这种组件关系,这可以帮助人们理解到底发生了什么// badexport default function withFoo(WrappedComponent) {return function WithFoo(props) {return <WrappedComponent {...props} foo />;}}// goodexport 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
命名组件。最好通过引用命名组件。// badexport default React.createClass({displayName: 'ReservationCard',// stuff goes here});// goodexport 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<FoosuperLongParam="bar"anotherSuperLongParam="baz"/>// 如果能放在一行,也可以用单行表示<Foo bar="bar" />// Foo 里面的标签正常缩进<FoosuperLongParam="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={{ 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} />
属性
props 用小驼峰
// bad<FooUserName="hello"phone_number={12345678}/>// good<FoouserName="hello"phoneNumber={12345678}/>如果 prop 的值是 true 可以忽略这个值,直接写 prop 名就可以。 eslint:
react/jsx-boolean-value
// bad<Foohidden={true}/>// good<Foohidden/>// 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
属性,推荐用稳定的 IDWhy? 不使用稳定杆的 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 是一个文档形式,同时提供默认属性意味着使用者不需要假定那么多值。另外,这也意味着你的代码可以忽略类型检查。
// badfunction SFC({ foo, bar, children }) {return <div>{foo}{bar}{children}</div>;}SFC.propTypes = {foo: PropTypes.number.isRequired,bar: PropTypes.string,children: PropTypes.node,};// goodfunction 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扩展运算符,既
{...props}
Why? 除非你更喜欢把不需要的props属性传入组件。而且对于 v15.6.1 及更早以前的 React, 你只能给DOM元素传非HTML属性的props。
例外:
HOC 是代理 props 并且提成了propTypes
function HOC(WrappedComponent) {return class Proxy extends React.Component {Proxy.propTypes = {text: PropTypes.string,isLoading: PropTypes.bool};render() {return <WrappedComponent {...this.props} />}}}扩展一个已知的,有明确属性的对象也是可以的。这个对用 Mocha 的 beforeEach 函数做单测时尤其有用。
export default function Foo {const props = {text: '',isPublished: false}return (<div {...props} />);}使用说明: 尽可能过滤出不需要的属性。同时用prop-type-exact去帮助避免bug。
// badrender() {const { irrelevantProp, ...relevantProps } = this.props;return <WrappedComponent {...this.props} />}// goodrender() {const { irrelevantProp, ...relevantProps } = this.props;return <WrappedComponent {...relevantProps} />}
Refs
推荐用 ref callback 函数。 eslint:
react/no-string-refs
// bad<Fooref="myRef"/>// good<Fooref={(ref) => { this.myRef = ref; }}/>
圆括号
当 JSX 标签有多行时,用圆括号包起来。eslint:
react/jsx-wrap-multilines
// badrender() {return <MyComponent variant="long body" foo="bar"><MyChild /></MyComponent>;}// goodrender() {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<Foobar="bar"baz="baz" />// good<Foobar="bar"baz="baz"/>
方法
用箭头函数关闭局部变量。
function ItemList(props) {return (<ul>{props.items.map((item, index) => (<Itemkey={item.key}onClick={() => doSomethingWith(item.name, index)}/>))}</ul>);}在构造函数里绑定事件处理函数。 eslint:
react/jsx-no-bind
Why? render 函数中的绑定调用在每次 render 的时候都会创建一个新的函数。
// badclass extends React.Component {onClickDiv() {// do stuff}render() {return <div onClick={this.onClickDiv.bind(this)} />;}}// goodclass extends React.Component {constructor(props) {super(props);this.onClickDiv = this.onClickDiv.bind(this);}onClickDiv() {// do stuff}render() {return <div onClick={this.onClickDiv} />;}}不要在 React 组件里使用下划线作为内部方法名前缀。
Why? 下划线前缀有时候在其他语言里被用于表示私有。但是 JavaScript 原生并不支持私有,所有东西都是公有的。尽管在你的意图里,对你的属性添加下划线前缀不是真的是他变成私有属性,而且任何属性(不论是不是下划线前缀)都被认为是公有的。详细讨论见问题#1024,和#490
// badReact.createClass({_onClickSubmit() {// do stuff},// other stuff});// goodclass extends React.Component {onClickSubmit() {// do stuff}// other stuff}确保你的
render
函数有返回值。 eslint:react/require-render-return
// badrender() {(<div />);}// goodrender() {return (<div />);}
属性排序
class extends React.Component
内部属性的顺序:
- 可选的
static
方法 constructor
getChildContext
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
- clickHandlers or eventHandlers 如:
onClickSubmit()
、onChangeDescription()
- getter methods for
render
如:getSelectReason()
、getFooterContent()
- optional render methods 如:
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
- clickHandlers or eventHandlers 如:
onClickSubmit()
、onChangeDescription()
- getter methods for
render
如:getSelectReason()
、getFooterContent()
- optional render methods 如:
renderNavigation()
、renderProfilePicture()
render
isMounted
不要用
isMounted
。 eslint:react/no-is-mounted
Why? [
isMounted
是反模式][anti-pattern], 这个在 ES6 class 里不允许的,而且即将被官方废弃。