主要知识点

  • shouldComponentUpdate(简称SCU)
  • PureComponent和React.memo
  • 不可变值immutable.js

SCU

基本用法

  1. shouldComponentUpdate(nextProps,nextState) {
  2. if(nextState.count !== this.state.count){
  3. return true //可以渲染
  4. }
  5. return false // 不重复渲染
  6. }

思考:为什么react定义了这样的生命周期,默认返回true,还给你一个定制的权利?

举例一:

先来看一段代码

  1. import React from "react";
  2. import PropTypes from "prop-types";
  3. // input组件
  4. class Input extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. this.state = {
  8. title: "",
  9. };
  10. }
  11. render() {
  12. return (
  13. <div>
  14. <input value={this.state.title} onChange={this.onTitleChange} />
  15. <button onClick={this.onSubmit}>提交</button>
  16. </div>
  17. );
  18. }
  19. onTitleChange = (e) => {
  20. this.setState({
  21. title: e.target.value,
  22. });
  23. };
  24. onSubmit = () => {
  25. const { submitTitle } = this.props;
  26. submitTitle(this.state.title); // 'abc'
  27. this.setState({
  28. title: "",
  29. });
  30. };
  31. }
  32. // props 类型检查
  33. Input.propTypes = {
  34. submitTitle: PropTypes.func.isRequired,
  35. };
  36. // list组件
  37. class List extends React.Component {
  38. constructor(props) {
  39. super(props);
  40. }
  41. render() {
  42. const { list } = this.props;
  43. return (
  44. <ul>
  45. {list.map((item, index) => {
  46. return (
  47. <li key={item.id}>
  48. <span>{item.title}</span>
  49. </li>
  50. );
  51. })}
  52. </ul>
  53. );
  54. }
  55. }
  56. // props 类型检查
  57. List.propTypes = {
  58. list: PropTypes.arrayOf(PropTypes.object).isRequired,
  59. };
  60. // footer组件
  61. class Footer extends React.Component {
  62. constructor(props) {
  63. super(props);
  64. }
  65. render() {
  66. return (
  67. <p>
  68. {this.props.text}
  69. {this.props.length}
  70. </p>
  71. );
  72. }
  73. componentDidUpdate() {
  74. console.log("footer did update");
  75. }
  76. shouldComponentUpdate(nextProps, nextState) {
  77. if (nextProps.text !== this.props.text) {
  78. return true;
  79. }
  80. return false;
  81. }
  82. }
  83. // 顶级组件
  84. class TodoListDemo extends React.Component {
  85. constructor(props) {
  86. super(props);
  87. // 状态(数据)提升
  88. this.state = {
  89. list: [
  90. {
  91. id: "id-1",
  92. title: "标题1",
  93. },
  94. {
  95. id: "id-2",
  96. title: "标题2",
  97. },
  98. {
  99. id: "id-3",
  100. title: "标题3",
  101. },
  102. ],
  103. footerInfo: "底部文字",
  104. };
  105. }
  106. render() {
  107. return (
  108. <div>
  109. <Input submitTitle={this.onSubmitTitle} />
  110. <List list={this.state.list} />
  111. <Footer text={this.state.footerInfo} length={this.state.list.length} />
  112. </div>
  113. );
  114. }
  115. onSubmitTitle = (title) => {
  116. this.setState({
  117. list: this.state.list.concat({
  118. id: `id-${Date.now()}`,
  119. title,
  120. }),
  121. });
  122. };
  123. }
  124. export default TodoListDemo;

image.png

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

image.png

image.png

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

image.png

image.png

我们加上了这一段代码后,发现footer组件并没有随着其他组件的更新而重新渲染。

举例二:

还是上面的代码结构,我在TodoListDemo和List做了一些改造。

TodoListDemo代码:

image.png

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

List代码:

image.png

效果:
image.png

我依旧输入了三次的值,但是发现列表并没有添加值,而且并没有重新渲染。这是为什么呢?

事实上用push方法更新数据时,传递的nextProps.list和this.props.list的值是相等的,所以没有进行重新渲染,不遵守不可变值的规范,所以会出现bug。

正确的写法使用slice或者concat。
image.png

注意:

添加 shouldComponentUpdate 方法时,不建议使用深度相等检查(如使用 JSON.stringify()),因为深比较效率很低,可能会比重新渲染组件效率还低。而且该方法维护比较困难,建议使用该方法会产生明显的性能提升时使用。

小结:

  • React 默认:父组件有更新,子组件则无条件也更新!!!
  • 性能优化对于 React 更加重要!
  • 必须配合”不可变值”一起使用
  • SCU 一定要每次都用吗?—— 需要的时候才优化

PureComponent

定制了shouldComponentUpdate后的Component
React.PureComponentReact.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 props 和 state 的方式来实现了该函数。
如果赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。

  • PureComponent,SCU中实现了浅比较
  • 浅比较已使用大部分情况(尽量不要做深比较)

使用注意,要进行值比较,使用引用类型时确保地址不改变

  1. import React, { PureComponent } from "react";
  2. export default class PuerComponentPage extends PureComponent {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. counter: 0,
  7. obj: { num: 100 },
  8. };
  9. }
  10. setCounter = () => {
  11. this.setState({ counter: 1, obj: { num: 200 } });
  12. console.log("setCounter");
  13. };
  14. render() {
  15. console.log("render");
  16. const { counter, obj } = this.state;
  17. return (
  18. <div>
  19. <button onClick={this.setCounter}>setCounter</button>{" "}
  20. <div>counter: {counter}</div>
  21. <div>obj.num: {obj.num}</div>
  22. </div>
  23. );
  24. }
  25. }

缺点是必须要用class形式,而且要注意是浅⽐较

React.memo

React.memo(…) 是React v16.6引进来的新属性。它的作⽤和 React.PureComponent 类似,是⽤来控制函数组件的重新渲染的。 React.memo(…) 其实就是函数组件的 React.PureComponent。

  1. import React, { Component, memo } from "react";
  2. export default class MemoPage extends Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. counter: 0,
  7. obj: { num: -1 },
  8. };
  9. }
  10. setCounter = () => {
  11. this.setState({ counter: 1 /* , obj: {
  12. num: 100,
  13. }, */ });
  14. };
  15. render() {
  16. const { counter } = this.state;
  17. return (
  18. <div>
  19. <h1>MemoPage</h1>
  20. <button onClick={this.setCounter}>按钮</button>
  21. {/* <PuerCounter counter={counter} obj={obj} /> */} // 浅比较,使用引用类型失去作用
  22. <PuerCounter counter={counter} />
  23. </div>
  24. );
  25. }
  26. }
  27. const PuerCounter = memo((props) => {
  28. console.log("render");
  29. return <div>{props.counter}</div>;
  30. });

Immutable.js

  • 彻底拥抱不可变值
  • 基于共享数据(不是深拷贝),速度好
  • 有一定学习和迁移成本,按需求使用