基础介绍
最近 react 的代码写的比较多,代码的编码规范并不明确,导致项目中出现不一样的命名风格。考虑了一下,还是需要统一一下前端的编码规范,搜索了 react 前端的编码规范,看到目前使用最多的应该是 Airbnb 的规范,于是我们也引入进来,有了统一的编码规范才能有统一的代码嘛,具体内容参考自 Airbnb React/JSX Style Guide 。
具体规范
基本规则
- 在单个文件只包含一个 React 组件
- 多个无状态,Pure 组件是允许的, eslint
- 始终使用 JSX 语法
- 不要使用
React.createElement
,除非是在通过非 JSX 文件初始化 app 的情况下 - react/forbid-prop-types 此规则会禁用
arrays
和objects
,除非显式声明arrays
和objects
中包含的内容,使用arrayOf
,objectOf
, 或shape
Class vs 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,倾向于使用普通的函数(不要选择箭头函数),而不是 classes
// 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 会引入隐式依赖,导致命名冲突,导致雪球式的复杂度。在大多数情况下 Mixins 可以被更好的方法替代,如:组件化,高阶组件,工具模块等。
命名
-
扩展名: 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
时,应该生成一个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; }
-
Props 命名:比如使用 DOM 组件的 prop 名字用于不同的目的
为什么呢?开发人员预期像
style
和className
这样的 props 意味着特定的事情。在你的 app 子集中改变这种 API 会让你的代码可读性和可维护性更差,而且也可能会导致 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:
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> // bad {showButton && <Button /> } // bad { showButton && <Button /> } // good {showButton && ( <Button /> )} // good {showButton && <Button />}
引号
-
对于 JSX 属性使用双引号(
"
), 其他的 JS 均使用单引号('
)。eslint:jsx-quotes
为什么呢?常规的 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 属性
-
对于 prop 的名字应该采用驼峰命名
// bad <Foo UserName="hello" phone_number={12345678} /> // good <Foo userName="hello" phoneNumber={12345678} />
-
在 prop 值为 true 时可以省略这个值。 eslint:
react/jsx-boolean-value
// bad <Foo hidden={true} /> // good <Foo hidden /> // good <Foo hidden />
-
<img>
标签总要包含alt
属性。如果是装饰性的图片 presentational image ,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”, or “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 - 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 是一种 反模式 , 因为这样会影响性能,也会导致组件状态异常
如果这个列表的顺序会改变的话,不推荐使用键的索引。
// bad {todos.map((todo, index) => <Todo {...todo} key={index} /> )} // good {todos.map(todo => ( <Todo {...todo} key={todo.id} /> ))}
-
对于非必须的参数 props,始终定义默认参数 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 及以前的版本,你可以传递无效的 HTML 属性给 DOM
例外情况:
-
HOCs that proxy down props and hoist propTypes
function HOC(WrappedComponent) { return class Proxy extends React.Component { Proxy.propTypes = { text: PropTypes.string, isLoading: PropTypes.bool }; render() { return <WrappedComponent {...this.props} /> } } }
-
在 props 明确时可以使用扩展运算符。这在使用 Mocha 的 beforeEach 构造去测试组件时非常有用
export default function Foo { const props = { text: '', isPublished: false } return (<div {...props} />); }
使用提醒:过滤掉不必要的 props。同时,使用 prop-types-exact 去帮助预防 bug
// bad render() { const { irrelevantProp, ...relevantProps } = this.props; return <WrappedComponent {...this.props} /> } // good render() { const { irrelevantProp, ...relevantProps } = this.props; return <WrappedComponent {...relevantProps} /> }
-
Refs
-
始终使用 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, when single line render() { const body = <div>hello</div>; return <MyComponent>{body}</MyComponent>; }
Tags 标签
-
在没有子元素的情况下使用自闭合标签。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={(event) => doSomethingWith(event, item.name, index)} /> ))} </ul> ); }
-
在构造函数中为渲染的内容绑定事件处理方法。eslint:
react/jsx-no-bind
为什么?在 render 过程中每次调用 bind 都会创建一个新的方法。在 class 字段中不要使用箭头函数,因为这会让 测试和调试变得富有挑战,并且严重影响性能 ,并且从概念上,class 字段是用于表示数据,而不是逻辑。
// bad class extends React.Component { onClickDiv() { // do stuff } render() { return <div onClick={this.onClickDiv.bind(this)} />; } } // very bad class extends React.Component { onClickDiv = () => { // do stuff } render() { return <div onClick={this.onClickDiv} /> } } // 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 中的 getter 方法 比如
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 中的 getter 方法 比如
getSelectReason()
或getFooterContent()
- 可选的 render 方法 比如
renderNavigation()
或renderProfilePicture()
render
isMounted
-
不要使用
isMounted
。eslint:react/no-is-mounted
为什么?
isMounted
是反模式 ,在 ES6 的 classes 中不可用。而且处在被官方废弃的路上。