web-standard/javascript/react/README.md

573 lines
16 KiB
Markdown
Raw Permalink Normal View History

2016-08-24 09:49:21 +08:00
# React/JSX 规范指南
*算是最合理的 React/JSX 编码规范之一了*
## 内容目录
1. [基本规范](#basic-rules-基本规范)
1. [Class vs React.createClass vs stateless](#创建模块)
1. [命名](#naming-命名)
1. [声明模块](#declaration-声明模块)
1. [代码对齐](#alignment-代码对齐)
1. [单引号还是双引号](#quotes-单引号还是双引号)
1. [空格](#spacing-空格)
1. [属性](#props-属性)
1. [Refs引用](#refs)
1. [括号](#parentheses-括号)
1. [标签](#tags-标签)
1. [函数/方法](#methods-函数)
1. [模块生命周期](#ordering-react-模块生命周期)
1. [isMounted](#ismounted)
## Basic Rules 基本规范
- 每个文件只写一个模块.
- 但是多个[无状态模块](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions)可以放在单个文件中. eslint: [`react/no-multi-comp`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md#ignorestateless).
- 推荐使用JSX语法.
- 不要使用 `React.createElement`除非从一个非JSX的文件中初始化你的app.
## 创建模块
Class vs React.createClass vs stateless
- 如果你的模块有内部状态或者是`refs`, 推荐使用 `class extends React.Component` 而不是 `React.createClass` ,除非你有充足的理由来使用这些方法.
eslint: [`react/prefer-es6-class`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md) [`react/prefer-stateless-function`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md)
```jsx
// 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` 推荐使用普通函数(非箭头函数)而不是类:
```jsx
// 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>;
}
```
## Naming 命名
- **扩展名**: React模块使用 `.jsx` 扩展名.
- **文件名**: 文件名使用驼峰式. 如, `ReservationCard.jsx`.
- **引用命名**: React模块名使用驼峰式命名实例使用骆驼式命名. eslint: [`react/jsx-pascal-case`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md)
```jsx
// bad
import reservationCard from './ReservationCard';
// good
import ReservationCard from './ReservationCard';
// bad
const ReservationItem = <ReservationCard />;
// good
const reservationItem = <ReservationCard />;
```
- **模块命名**: 模块使用当前文件名一样的名称. 比如 `ReservationCard.jsx` 应该包含名为 `ReservationCard`的模块. 但是,如果整个文件夹是一个模块,使用 `index.js`作为入口文件,然后直接使用 `index.js` 或者文件夹名作为模块的名称:
```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` 可能会在开发者工具或者错误信息中使用到因此有一个能清楚的表达这层关系的值能帮助我们更好的理解模块发生了什么更好的Debug.
```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;
}
```
## Declaration 声明模块
- 不要使用 `displayName` 来命名React模块而是使用引用来命名模块 如 class 名称.
```jsx
// bad
export default React.createClass({
displayName: 'ReservationCard',
// stuff goes here
});
// good
export default class ReservationCard extends React.Component {
}
```
## Alignment 代码对齐
- 遵循以下的JSX语法缩进/格式. eslint: [`react/jsx-closing-bracket-location`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md)
```jsx
// bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
// good, 有多行属性的话, 新建一行关闭标签
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// 若能在一行中显示, 直接写成一行
<Foo bar="bar" />
// 子元素按照常规方式缩进
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Quux />
</Foo>
```
## Quotes 单引号还是双引号
- 对于JSX属性值总是使用双引号(`"`), 其他均使用单引号. eslint: [`jsx-quotes`](http://eslint.org/docs/rules/jsx-quotes)
> 为什么? JSX属性 [不能包括转译的引号](http://eslint.org/docs/rules/jsx-quotes), 因此在双引号里包括像 `"don't"` 的属性值更容易输入.
> HTML属性也是用双引号所以JSX属性也遵循同样的语法.
```jsx
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />
```
## Spacing 空格
- 总是在自动关闭的标签前加一个空格,正常情况下也不需要换行.
```jsx
// bad
<Foo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Foo />
```
- 不要在JSX `{}` 引用括号里两边加空格. eslint: [`react/jsx-curly-spacing`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md)
```jsx
// bad
<Foo bar={ baz } />
// good
<Foo bar={baz} />
```
## Props 属性
- JSX属性名使用骆驼式风格`camelCase`.
```jsx
// bad
<Foo
UserName="hello"
phone_number={12345678}
/>
// good
<Foo
userName="hello"
phoneNumber={12345678}
/>
```
- 如果属性值为 `true`, 可以直接省略. eslint: [`react/jsx-boolean-value`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md)
```jsx
// bad
<Foo
hidden={true}
/>
// good
<Foo
hidden
/>
```
- `<img>` 标签总是添加 `alt` 属性. 如果图片以presentation(感觉是以类似PPT方式显示?)方式显示,`alt` 可为空, 或者`<img>` 要包含`role="presentation"`. eslint: [`jsx-a11y/img-has-alt`](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-has-alt.md)
```jsx
// 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" />
```
- 不要在 `alt` 值里使用如 "image", "photo", or "picture"包括图片含义这样的词, 中文也一样. eslint: [`jsx-a11y/img-redundant-alt`](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-redundant-alt.md)
> 为什么? 屏幕助读器已经把 `img` 标签标注为图片了, 所以没有必要再在 `alt` 里说明了.
```jsx
// bad
<img src="hello.jpg" alt="Picture of me waving hello" />
// good
<img src="hello.jpg" alt="Me waving hello" />
```
- 使用有效正确的 aria `role`属性值 [ARIA roles](https://www.w3.org/TR/wai-aria/roles#role_definitions). eslint: [`jsx-a11y/aria-role`](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-role.md)
```jsx
// 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`](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-access-key.md)
> 为什么? 屏幕助读器在键盘快捷键与键盘命令时造成的不统一性会导致阅读性更加复杂.
```jsx
// bad
<div accessKey="h" />
// good
<div />
```
- 避免使用数组的index来作为属性`key`的值推荐使用唯一ID. ([为什么?](https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318))
```jsx
// bad
{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}
// good
{todos.map(todo => (
<Todo
{...todo}
key={todo.id}
/>
))}
```
## Refs
- 总是在Refs里使用回调函数. eslint: [`react/no-string-refs`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md)
```jsx
// bad
<Foo
ref="myRef"
/>
// good
<Foo
ref={ref => { this.myRef = ref; }}
/>
```
## Parentheses 括号
- 将多行的JSX标签写在 `()`里. eslint: [`react/wrap-multilines`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md)
```jsx
// bad
render() {
return <MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>;
}
// good
render() {
return (
<MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
// good, 单行可以不需要
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
```
## Tags 标签
- 对于没有子元素的标签来说总是自己关闭标签. eslint: [`react/self-closing-comp`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md)
```jsx
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
```
- 如果模块有多行的属性, 关闭标签时新建一行. eslint: [`react/jsx-closing-bracket-location`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md)
```jsx
// bad
<Foo
bar="bar"
baz="baz" />
// good
<Foo
bar="bar"
baz="baz"
/>
```
## Methods 函数
- 使用箭头函数来获取本地变量.
```jsx
function ItemList(props) {
return (
<ul>
{props.items.map((item, index) => (
<Item
key={item.key}
onClick={() => doSomethingWith(item.name, index)}
/>
))}
</ul>
);
}
```
- 当在 `render()` 里使用事件处理方法时,提前在构造函数里把 `this` 绑定上去. eslint: [`react/jsx-no-bind`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md)
> 为什么? 在每次 `render` 过程中, 再调用 `bind` 都会新建一个新的函数,浪费资源.
```jsx
// 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模块中不要给所谓的私有函数添加 `_` 前缀,本质上它并不是私有的.
```jsx
// bad
React.createClass({
_onClickSubmit() {
// do stuff
},
// other stuff
});
// good
class extends React.Component {
onClickSubmit() {
// do stuff
}
// other stuff
}
```
-`render` 方法中总是确保 `return` 返回值. eslint: [`require-render-return`](https://github.com/yannickcr/eslint-plugin-react/pull/502)
```jsx
// bad
render() {
(<div />);
}
// good
render() {
return (<div />);
}
```
## Ordering React 模块生命周期
- `class extends React.Component` 的生命周期函数:
1. 可选的 `static` 方法
1. `constructor` 构造函数
1. `getChildContext` 获取子元素内容
1. `componentWillMount` 模块渲染前
1. `componentDidMount` 模块渲染后
1. `componentWillReceiveProps` 模块将接受新的数据
1. `shouldComponentUpdate` 判断模块需不需要重新渲染
1. `componentWillUpdate` 上面的方法返回 `true` 模块将重新渲染
1. `componentDidUpdate` 模块渲染结束
1. `componentWillUnmount` 模块将从DOM中清除, 做一些清理任务
1. *点击回调或者事件处理器*`onClickSubmit()``onChangeDescription()`
1. *`render` 里的 getter 方法*`getSelectReason()``getFooterContent()`
1. *可选的 render 方法*`renderNavigation()``renderProfilePicture()`
1. `render` render() 方法
- 如何定义 `propTypes`, `defaultProps`, `contextTypes`, 等等其他属性...
```jsx
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` 的生命周期函数与使用class稍有不同: eslint: [`react/sort-comp`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md)
1. `displayName` 设定模块名称
1. `propTypes` 设置属性的类型
1. `contextTypes` 设置上下文类型
1. `childContextTypes` 设置子元素上下文类型
1. `mixins` 添加一些mixins
1. `statics`
1. `defaultProps` 设置默认的属性值
1. `getDefaultProps` 获取默认属性值
1. `getInitialState` 或者初始状态
1. `getChildContext`
1. `componentWillMount`
1. `componentDidMount`
1. `componentWillReceiveProps`
1. `shouldComponentUpdate`
1. `componentWillUpdate`
1. `componentDidUpdate`
1. `componentWillUnmount`
1. *clickHandlers or eventHandlers* like `onClickSubmit()` or `onChangeDescription()`
1. *getter methods for `render`* like `getSelectReason()` or `getFooterContent()`
1. *Optional render methods* like `renderNavigation()` or `renderProfilePicture()`
1. `render`
## isMounted
- 不要再使用 `isMounted`. eslint: [`react/no-is-mounted`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md)
> 为什么? [`isMounted` 反人类设计模式:()][anti-pattern], 在 ES6 classes 中无法使用, 官方将在未来的版本里删除此方法.
[anti-pattern]: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
**[⬆ 回到顶部](#内容目录)**