本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
前言
规则来源于
- eslint-plugin-react 提供的规则,选择出有利于我们当前项目的部分;
- React 社区中的讨论。
愿望
- 每一条规则都应有详尽的解释、示例代码,能直观看到规则的作用、得知使用该规则的原因;
- 每一条规则都可以使用 eslint 进行自动化检查;
- 已有项目中完全没有违反的规则无需列出。
JSX
[强制] 没有子节点的组件使用自闭合语法(react/self-closing-comp)
解释:
JSX与HTML不同,所有元素均可以自闭合。
// Bad
<Foo></Foo>
<div></div>
// Good
<Foo />
<div />
复制代码
[强制] 保持起始和结束标签在同一层缩进(react/jsx-wrap-multilines)
解释:
代码样式。
// Bad
class Message {
render() {
return <div>
<span>Hello World</span>
</div>;
}
}
// Good
class Message {
render() {
return (
<div>
<span>Hello World</span>
</div>;
);
}
}
复制代码
[强制] 自闭合标签的/>前添加一个空格(react/jsx-space-before-closing)
解释:
代码样式。
// Bad
<Foo bar="bar"/>
<Foo bar="bar" />
// Good
<Foo bar="bar" />
复制代码
API
[强制] 禁止为继承自 PureComponent 的组件编写 shouldComponentUpdate 实现(react/no-redundant-should-component-update)
解释:
在 React 的实现中,PureComponent 并不直接实现 shouldComponentUpdate,而是添加一个 isReactPureComponent 的标记,由 CompositeComponent 通过识别这个标记实现相关的逻辑。因此在 PureComponent 上自定义 shouldComponentUpdate 并无法享受 super.shouldComponentUpdate 的逻辑复用,也会使得这个继承关系失去意义。
相关的 issue:github.com/facebook/re…
补充:
- 为了避免组件无效的 render 导致的性能开销使用 PureComponet 和 memo 是好的,但请记住它们也是存在开销的,请勿滥用。
相关的 issue:github.com/facebook/re…
- 使用 react-redux 中 connect 方法包裹的组件没有继承 PureComponent 或使用 memo 方法的必要。
相关的代码:github.com/reduxjs/rea…
- 可使用 React Developer Tools 中开启 Highlight updates when components render 设置项观察组件渲染情况。
[强制] 禁止使用 String 类型的 Refs(react/no-string-refs)
解释:
它已过时并可能会在 React 未来的版本被移除。
补充:
相关 issue:github.com/facebook/re…
String 类型的 Refs 存在的问题:
- 它要求 React 跟踪当前渲染的组件,这会导致 React 的执行稍微慢一些。
- 它不能像大多数人期望的那样使用“渲染回调(render callback)”模式。
class MyComponent extends Component {
renderRow = (index) => {
// 这将无法工作,ref 将被添加到 DataTable 上,而非 MyComponent 上:
return <input ref={'input-' + index} />;
// This would work though! Callback refs are awesome.
return <input ref={input => this['input-' + index] = input} />;
}
render() {
return <DataTable data={this.props.data} renderRow={this.renderRow} />
}
}
复制代码
- 它是不可组合的,也就是说,如果库在传递的子对象上放一个 ref,用户就不能再放一个 ref。回调引用是可组合的。
[强制] 避免使用不安全的生命周期函数 componentWillMount、componentWillReceiveProps、componentWillUpdate(react/no-unsafe)
解释:
以上生命周期函数被 React 官方视为是不安全的,公司 react 规范中推荐使用 constructor 代替 componentWillMount,具体生命周期迁移参考『迁移过时的生命周期』。
备注:
React 官方视为以上生命周期不安全的原因是:对于 Concurrent 模式(实验性)这个未来特性,避免在 willMount / willUpdate 等生命周期挂钩中产生副作用非常重要。因为 React 当前的渲染分为 reconcile 和 commit 两个阶段,reconcile 阶段可以被高权重用户事件中端导致重复执行,由于以上生命周期函数在此阶段中被调用,导致这些生命周期函数存在被重复调用的可能。
React RFC 0006-static-lifecycle-methods:github.com/reactjs/rfc…
React v16.9.0 更新日志:react.docschina.org/blog/2019/0…
迁移过时的生命周期:react.docschina.org/blog/2018/0…
Concurrent 模式介绍 (实验性):react.docschina.org/docs/concur…
(:з[__] Dan 宝在 stackoverflow 上的回答 – 在 React 中,我应该在 componentWillMount 还是 componentDidMount 中进行初始网络请求?stackoverflow.com/a/41612993
[强制] 禁止使用数组索引作为 key(react/no-array-index-key)
解释:
React 使用 key 来判断哪些元素已经改变、添加或删除。
补充:
在此多说一些,我想一定有人和我之前的一样对 key 存在这样的误解,key 的目的是为了更好的性能。不,这不是事实。
key 的主要目的是作为唯一标识,在 fiber diff 阶段能够正确地判断出元素改变、添加或删除状态,尤其是对自身拥有状态的组件,这非常重要。
而在此前提下,对于属性没有变更的组件,只有实现了 shouldComponentUpdate / memo 方法,才可以避免重复渲染。
相关 issue:github.com/facebook/re…
Understanding the key prop: stackoverflow.com/questions/2…
// Bad
function Posts() {
const [list, setList] = useState([]);
useEffect(() => {
fetchPosts().then(setList);
}, []);
return list.map((post, index) => <input key={index} defaultValue={post.title} />);
}
// Good
function Posts() {
const [list, setList] = useState([]);
useEffect(() => {
fetchPosts().then(setList);
}, []);
return list.map(post => <input key={post.id} defaultValue={post.title} />);
}
// Good - 如果没有 id 等唯一标识,可以由前端主动生成唯一标识
function Posts() {
const [list, setList] = useState([]);
useEffect(() => {
fetchPosts().then(list => {
for (const post of list) {
post.id = SomeLibrary.generateUniqueID();
}
setList(list);
});
}, []);
return list.map(post => <input key={post.id} defaultValue={post.title} />);
}
复制代码
[建议] 避免在JSX的属性值中直接使用对象和函数表达式(react/jsx-no-bind)
解释:
PureComponent 使用 shallowEqual 对 props 和 state 进行比较来决定是否需要渲染,而在 JSX 的属性值中使用对象、函数表达式会造成每一次的对象引用不同,从而 shallowEqual 会返回 false,导致不必要的渲染。
补充:
函数组件中为避免子组件刷新,可使用 useCallback 和 useMemo 等 hook 来保持函数、对象类型的属性引用不变。
// Bad
class WarnButton {
alertMessage(message) {
alert(message);
}
render() {
return <button type="button" onClick={() => this.alertMessage(this.props.message)}>提示</button>
}
}
// Good
class WarnButton {
@bind()
alertMessage() {
alert(this.props.message);
}
render() {
return <button type="button" onClick={this.alertMessage}>提示</button>
}
}
// Bad
function Foo() {
async function handleOk() {
try {
await fetch();
} catch (error) {
message.error('error');
return;
}
message.success('success');
}
return <Modal onOk={handleOk} />;
}
// Good
function Foo() {
const handleOk = useCallback(async () => {
try {
await fetch();
} catch (error) {
message.error('error');
return;
}
message.success('success');
}, []);
return <Modal onOk={handleOk} />;
}
复制代码
[建议] 禁止在函数组件上使用 defaultProps(react/require-default-props,ignoreFunctionalComponents: true)
解释:
defaultProps 在类中非常有用,因为 props 对象被传递给许多不同的方法,如生命周期、回调等。每一个都有自己的作用域。这导致使用 JS 默认参数变得困难,因为您必须在每个函数中反复确定相同的默认值。
class Foo {
static defaultProps = {foo: 1};
componentDidMount() {
let foo = this.props.foo;
console.log(foo);
}
componentDidUpdate() {
let foo = this.props.foo;
console.log(foo);
}
componentWillUnmount() {
let foo = this.props.foo;
console.log(foo);
}
handleClick = () => {
let foo = this.props.foo;
console.log(foo);
}
render() {
let foo = this.props.foo;
console.log(foo);
return <div onClick={this.handleClick} />;
}
}
复制代码
但是,在函数组件中,实际上不需要这种模式,因为您可以只使用 JS 默认参数,并且通常这些值的所有使用的位置都在同一作用域内。
function Foo({foo = 1}) {
useEffect(() => {
console.log(foo);
return () => {
console.log(foo);
};
});
let handleClick = () => {
console.log(foo);
};
console.log(foo);
return <div onClick={handleClick} />;
}
复制代码
React 团队之后的计划为,当在没有 .prototype.isReactComponent
的组件上使用 defaultProps
时,createElement 将发出警告。这包括那些特殊的组件,如 forwardRef
和 memo
。
如果 props 整体进行传递,那么升级将变得困难,不过你总是可以在需要时对它进行重构。
function Foo({foo = 1, bar = “hello”}) {
let props = {foo, bar};
//…
}
补充:
React RFC 0000-create-element-changes:0000-create-element-changes.md/ github.com/reactjs/rfc…
[注意] 单页应用中应避免使用 location.href = ‘https://juejin.cn/post/url to jump’ 进行应用内部跳转
解释:
location.href = 'url to jump' 会导致页面刷新导致重新请求静态资源。
// Bad
function Foo() {
const handleClick = useCallback(() => {
location.href = '/path/to/jump';
});
return <div onClick={handleClick}>点击</div>;
}
// Good
function Foo() {
const history = useHistory();
const handleClick = useCallback(() => {
// 在 react-router 中使用其 history 对象进行跳转
history.push('/path/to/jump');
});
return <div onClick={handleClick}>点击</div>;
}
复制代码