主要知识点
- shouldComponentUpdate(简称SCU)
- PureComponent和React.memo
- 不可变值immutable.js
SCU
基本用法
shouldComponentUpdate(nextProps,nextState) {if(nextState.count !== this.state.count){return true //可以渲染}return false // 不重复渲染}
思考:为什么react定义了这样的生命周期,默认返回true,还给你一个定制的权利?
举例一:
先来看一段代码
import React from "react";import PropTypes from "prop-types";// input组件class Input extends React.Component {constructor(props) {super(props);this.state = {title: "",};}render() {return (<div><input value={this.state.title} onChange={this.onTitleChange} /><button onClick={this.onSubmit}>提交</button></div>);}onTitleChange = (e) => {this.setState({title: e.target.value,});};onSubmit = () => {const { submitTitle } = this.props;submitTitle(this.state.title); // 'abc'this.setState({title: "",});};}// props 类型检查Input.propTypes = {submitTitle: PropTypes.func.isRequired,};// list组件class List extends React.Component {constructor(props) {super(props);}render() {const { list } = this.props;return (<ul>{list.map((item, index) => {return (<li key={item.id}><span>{item.title}</span></li>);})}</ul>);}}// props 类型检查List.propTypes = {list: PropTypes.arrayOf(PropTypes.object).isRequired,};// footer组件class Footer extends React.Component {constructor(props) {super(props);}render() {return (<p>{this.props.text}{this.props.length}</p>);}componentDidUpdate() {console.log("footer did update");}shouldComponentUpdate(nextProps, nextState) {if (nextProps.text !== this.props.text) {return true;}return false;}}// 顶级组件class TodoListDemo extends React.Component {constructor(props) {super(props);// 状态(数据)提升this.state = {list: [{id: "id-1",title: "标题1",},{id: "id-2",title: "标题2",},{id: "id-3",title: "标题3",},],footerInfo: "底部文字",};}render() {return (<div><Input submitTitle={this.onSubmitTitle} /><List list={this.state.list} /><Footer text={this.state.footerInfo} length={this.state.list.length} /></div>);}onSubmitTitle = (title) => {this.setState({list: this.state.list.concat({id: `id-${Date.now()}`,title,}),});};}export default TodoListDemo;

我们输入一些值来看一下效果。


可以发现我们的footer组件,每一次提交都要更新一次。假如我们有这样一个场景,就是Footer的数据量很大,而且List和Input的组件值不会影响到我们的Footer组件,不需要进行每一次重新渲染,这时那么我们的shouldComponentUpdate就派上了用场了。


我们加上了这一段代码后,发现footer组件并没有随着其他组件的更新而重新渲染。
举例二:
还是上面的代码结构,我在TodoListDemo和List做了一些改造。
TodoListDemo代码:

注意看这里,我们先是做了一个push的操作,然后在进行赋值
List代码:

效果:
我依旧输入了三次的值,但是发现列表并没有添加值,而且并没有重新渲染。这是为什么呢?
事实上用push方法更新数据时,传递的nextProps.list和this.props.list的值是相等的,所以没有进行重新渲染,不遵守不可变值的规范,所以会出现bug。
正确的写法使用slice或者concat。
注意:
添加 shouldComponentUpdate 方法时,不建议使用深度相等检查(如使用 JSON.stringify()),因为深比较效率很低,可能会比重新渲染组件效率还低。而且该方法维护比较困难,建议使用该方法会产生明显的性能提升时使用。
小结:
- React 默认:父组件有更新,子组件则无条件也更新!!!
- 性能优化对于 React 更加重要!
- 必须配合”不可变值”一起使用
- SCU 一定要每次都用吗?—— 需要的时候才优化
PureComponent
定制了shouldComponentUpdate后的Component React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 props 和 state 的方式来实现了该函数。
如果赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。
- PureComponent,SCU中实现了浅比较
- 浅比较已使用大部分情况(尽量不要做深比较)
使用注意,要进行值比较,使用引用类型时确保地址不改变
import React, { PureComponent } from "react";export default class PuerComponentPage extends PureComponent {constructor(props) {super(props);this.state = {counter: 0,obj: { num: 100 },};}setCounter = () => {this.setState({ counter: 1, obj: { num: 200 } });console.log("setCounter");};render() {console.log("render");const { counter, obj } = this.state;return (<div><button onClick={this.setCounter}>setCounter</button>{" "}<div>counter: {counter}</div><div>obj.num: {obj.num}</div></div>);}}
缺点是必须要用class形式,而且要注意是浅⽐较
React.memo
React.memo(…) 是React v16.6引进来的新属性。它的作⽤和 React.PureComponent 类似,是⽤来控制函数组件的重新渲染的。 React.memo(…) 其实就是函数组件的 React.PureComponent。
import React, { Component, memo } from "react";export default class MemoPage extends Component {constructor(props) {super(props);this.state = {counter: 0,obj: { num: -1 },};}setCounter = () => {this.setState({ counter: 1 /* , obj: {num: 100,}, */ });};render() {const { counter } = this.state;return (<div><h1>MemoPage</h1><button onClick={this.setCounter}>按钮</button>{/* <PuerCounter counter={counter} obj={obj} /> */} // 浅比较,使用引用类型失去作用<PuerCounter counter={counter} /></div>);}}const PuerCounter = memo((props) => {console.log("render");return <div>{props.counter}</div>;});
Immutable.js
- 彻底拥抱不可变值
- 基于共享数据(不是深拷贝),速度好
- 有一定学习和迁移成本,按需求使用
