目录
基本规范
- 每个文件只写一个模块;多个无状态函数组件可以放在单个文件中。 eslint:
react/no-multi-comp - 推荐使用 JSX 语法。
- 不要使用
React.createElement,除非从非 JSX 文件初始化应用。
创建模块
Class /createClass/ 无状态组件选择
- 组件存在内部 state 或者 ref,推荐使用
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>;
}
}
-
组件无状态、无 ref,推荐使用普通 function 函数,不要用 class,不推荐依赖名称推断的箭头函数
// bad
class Listing extends React.Component {
render() {
return{this.props.hello};
}
}// bad (不推荐依赖函数名推断)
const Listing = ({ hello }) => ({hello});// good
function Listing({ hello }) {
return{hello};
}
命名
eslint:
react/jsx-pascal-case
-
文件名 :帕斯卡命名,例:
ReservationCard.jsx -
组件引用:React 组件名帕斯卡命名;组件实例使用小驼峰命名
// bad
import reservationCard from './ReservationCard';
// good
import ReservationCard from './ReservationCard';// bad
const ReservationItem =;
// good
const reservationItem =; -
模块命名 :组件名和文件名保持一致;文件夹作为组件入口时,使用
index.js,直接引入文件夹名// bad
import Footer from './Footer/Footer';
// bad
import Footer from './Footer/index';
// good
import Footer from './Footer'; -
高阶组件 HOC :生成组件的
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做业务标记)// bad
// good
声明模块
-
不要手动写
displayName,直接使用 class / 函数名称作为组件名称// bad
export default React.createClass({
displayName: 'ReservationCard',
});// good
export default class ReservationCard extends React.Component {
} -
禁止使用
require引入组件 / 图片,统一使用 ES6import语法// bad
const qrCodeImg = require('@/assets/img/enter.png');
// good
import qrCodeImg from '@/assets/img/enter.png';
代码对齐
eslint:
react/jsx-closing-bracket-location、react/jsx-closing-tag-location
-
多行属性:每个属性单独一行,闭合标签另起一行
-
单行可容纳全部属性:直接写一行
-
子元素正常缩进
// bad
// good,多行属性,闭合标签另起一行
// 单行容纳直接一行
// 子元素缩进
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
单引号还是双引号
eslint:
jsx-quotesJSX 属性值使用双引号";JS 普通字符串使用单引号',对齐 HTML 习惯。
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
空格
- 自闭合标签
/前面保留一个空格
eslint:
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} />
- JSX 属性等号两侧禁止空格
eslint:
react/jsx-equals-spacing
// bad
<Hello name = {firstname} />;
<Hello name ={firstname} />;
<Hello name= {firstname} />;
// good
<Hello name={firstname} />;
属性
-
JSX 属性名使用小驼峰
camelCase// bad
// good
-
属性值为
true时,直接省略值
eslint:
react/jsx-boolean-value
// bad
<Foo hidden={true} />
// good
<Foo hidden />
- 禁止使用未知 DOM 属性,class 写成
className
eslint:
react/no-unknown-property
// bad
<div class="hello">Hello World</div>
// good
<div className="hello">Hello World</div>
<img>标签必须写alt;纯装饰图片可以alt=""或增加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" />
- alt 文本不要包含 image /photo/picture 这类图片描述词
eslint:
jsx-a11y/img-redundant-alt
// bad
<img src="hello.jpg" alt="Picture of me waving hello" />
// good
<img src="hello.jpg" alt="Me waving hello" />
- 使用合法有效的 ARIA role,禁止抽象 role
eslint:
jsx-a11y/aria-role
// bad - 非法role
<div role="datepicker" />
// bad - 抽象role
<div role="range" />
// good
<div role="button" />
- 禁止使用
accessKey属性
eslint:
jsx-a11y/no-access-key
// bad
<div accessKey="h" />
// good
<div />
-
数组渲染列表,key不要使用数组 index,优先使用业务唯一 id
// bad
{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}// good
{todos.map(todo => (
<Todo
{...todo}
key={todo.id}
/>
))} -
非必传属性,显式定义
defaultProps,作为组件文档// bad
function SFC({ foo, bar, children }) {
return{foo}{bar}{children};
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};// good
function SFC({ foo, bar, children }) {
return{foo}{bar}{children};
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
SFC.defaultProps = {
bar: '',
children: null,
}; -
谨慎使用扩展运算符
{...props},尽量解构剔除不需要的属性;高阶组件为特例允许透传// good 剔除无关属性再透传
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...relevantProps} />
} -
JSX 单行最多 1 个 prop;超过 1 个就换行分行书写
eslint:
react/jsx-max-props-per-line
// bad
<Hello lastName="Smith" firstName="John" />;
// good
<Hello
firstName="John"
lastName="Smith"
/>;
- JSX 禁止重复属性
eslint:
react/jsx-no-duplicate-props
// bad
<Hello
name="John"
name="John"
/>;
// good
<Hello
firstName="John"
lastName="Smith"
/>;
- a 标签
target="_blank"必须增加rel="noopener noreferrer",防止安全漏洞
eslint:
react/jsx-no-target-blank
// bad
<a target='_blank' href="http://example.com"></a>
// good
<a target="_blank" rel="noopener noreferrer" href="http://example.com"></a>
引用
ref 优先使用回调 ref,禁止字符串 ref
eslint:
react/no-string-refs
// bad
<Foo ref="myRef" />
// good
<Foo ref={(ref) => { this.myRef = ref; }} />
括号
多行 JSX 返回必须包裹在()内;单行 JSX 不需要
eslint:
react/jsx-wrap-multilines
// 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>;
}
标签
- 无子女的标签必须自闭合
eslint:
react/self-closing-comp
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
- 多行属性时,自闭合标签
/>单独起一行
eslint:
react/jsx-closing-bracket-location
// bad
<Foo
bar="bar"
baz="baz" />
// good
<Foo
bar="bar"
baz="baz"
/>
函数
-
列表渲染事件回调,使用箭头函数捕获局部变量
function ItemList(props) {
return (
{props.items.map((item, index) => (
<Item
key={item.key}
onClick={() => doSomethingWith(item.name, index)}
/>
))}
);
} -
class 组件事件处理函数,推荐类属性箭头函数,避免 render 里 bind,减少每次渲染新建函数开销
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} />;
}
}
// very good 类属性箭头函数(推荐)
class extends React.Component {
onClickDiv = () => {
// do stuff
}
render() {
return <div onClick={this.onClickDiv} />
}
}
render必须显式return返回值
eslint:
react/require-render-return
// bad
render() {
(<div />);
}
// good
render() {
return (<div />);
}
- 禁止在
componentDidUpdate中调用setState
eslint:
react/no-did-update-set-state
// bad
class Hello extends React.Component {
componentDidUpdate() {
this.setState({
name: this.props.name.toUpperCase()
});
}
render() {
return <div>Hello {this.state.name}</div>;
}
};
// good
class Hello extends React.Component {
componentDidUpdate() {
this.props.onUpdate();
}
render() {
return <div>Hello {this.props.name}</div>;
}
};
- 禁止在
componentWillUpdate调用setState
eslint:
react/no-will-update-set-state
- 不要使用
ReactDOM.render返回的实例
eslint:
react/no-render-return-value
// bad
const inst = ReactDOM.render(<App />, document.body);
doSomethingWithInst(inst);
// good
ReactDOM.render(<App ref={doSomethingWithInst} />, document.body);
ReactDOM.render(<App />, document.body, doSomethingWithInst);
模块生命周期
React 16.3+ 生命周期规范
- 新增:
static getDerivedStateFromProps、getSnapshotBeforeUpdate - 标记 UNSAFE,禁止使用:
UNSAFE_componentWillMount/UNSAFE_componentWillUpdate/UNSAFE_componentWillReceiveProps
class 组件生命周期顺序
constructor构造函数static getDerivedStateFromProps组件接收新数据render()componentDidMount首次渲染完成shouldComponentUpdate判断是否重渲染getSnapshotBeforeUpdatecomponentDidUpdate更新渲染结束componentWillUnmount组件销毁,清理资源
方法命名约定
- 事件回调:
onClickSubmit()/onChangeDescription() - render 内 getter:
getSelectReason()/getFooterContent() - 分段渲染函数:
renderNavigation()/renderProfilePicture()
propTypes /defaultProps 示例
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;
A11Y 无障碍规范
全部规则来自
eslint-plugin-jsx-a11y
-
ARIA role 必须合法、非抽象;自定义组件不检测
// good
-
aria-* 属性名称必须合法
// bad
// good
-
ARIA 属性值类型合法(布尔属性只能 true/false)
// Bad
foo
// Good
-
img/area/input type=image/object 必须提供替代文本
// Bad
// Good
-
img alt 禁止冗余描述词 (image/photo/picture)
-
label 必须关联表单控件,两种方式:内部包裹控件 /htmlFor + id 绑定
// bad
// good
-
onMouseOver/onMouseOut必须配套onFocus/onBlur,支持键盘访问// good
void 0} onFocus={()=>void 0} />void 0} onBlur={()=>void 0} />交互式元素(带 onClick、role 交互角色)必须可聚焦
// bad
Submit
// good
Click me!
role 如果有必填 aria 属性,必须补齐
// bad
// good
不要使用大于 0 的 tabIndex,不要手动干预 tab 顺序
// Bad
foo
// Good
foo
bar标题 h1~h6 必须包含可读内容,不能整体 aria-hidden
// Bad
// GoodHeading Content!
html 标签必须设置 lang 属性
// Bad
// Good禁止使用
<marquee>、<blink>干扰阅读元素scope属性只能 放在<th>标签// bad
// goodEmoji 表情增加
role="img"与aria-label,支持读屏// good
🐼iframe 必须有非空 title
// Bad
// Goodaudio/video 媒体标签需要字幕 track;muted 视频例外
// good
Hook
eslint:
react-hooks/rules-of-hooks- 只能在顶层调用 Hook,不能放在循环、if 条件、嵌套函数内部;保证每次渲染 Hook 调用顺序完全一致。
- 只能在 React 函数组件 / 自定义 Hook 中调用 Hook,普通 JS 函数不能调用 Hook。
✅ 如果需要条件逻辑,条件写在 useEffect 内部,不要包裹 useEffect
// 正确示例 function Form() { const [name, setName] = useState('Mary'); useEffect(function persistForm() { // 判断写在effect里面 if (name !== '') { localStorage.setItem('formData', name); } }); const [surname, setSurname] = useState('Poppins'); useEffect(function updateTitle() { document.title = name + ' ' + surname; }); }原理:React 依靠 Hook 调用顺序绑定 state,顺序改变会造成状态错乱。