React性能优化,SCU,PureComponent/memo,key的优化

199 阅读3分钟

React更新机制

  • React渲染流程: JSX -> 虚拟DOM -> 真实DOM
  • React更新流程: props/state改变 -> render函数重新执行 -> 产生新的DOM树 -> 新旧DOM树进行diff -> 计算出差异进行更新 -> 更新到真实的DOM

render函数被调用

只要修改了App中的数据,所有组件都需要重新render,进行diff算法,性能低,实际上,很多组件没有必要重新render,调用render应该有一个前提,就是依赖的数据(state,props)发生改变,再调用自己的render方法。 如何控制render函数是否被调用?

  • 通过shouldComponentUpdate()

React的更新流程

  • React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。
  • React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI
  • 如果两颗树进行完全比较更新,React的更新性能会非常低效
  • 于是,React对这个算法进行优化,将其优化成O(n)
  • 即,同层节点之间互相比较,不会垮节点比较
  • 不同类型的节点,产生不同的树结构
  • 通过key来指定哪些节点在不同的渲染下保持稳定

key的优化

  • 在最后位置插入数据(有无key意义不大)
  • 在前面插入数据(在没有key的情况下,所有的li都需要进行修改)
  • 当子元素(li)拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素,位移即可,无需修改
  • key应该是唯一的
  • key不要使用随机数(随机数在下一次render时,会重新生成一个数字)
  • 使用index作为key,对性能是没有优化的

shouldComponentUpdate简称SCU

  • 参数1:newProps,修改之后,最新的props属性
  • 参数2:newState,修改之后,最新的state属性
  • 参数3: context

该方法返回值是boolean类型

  • 返回值为true,调用render()
  • false,不调用render()
  • 默认返回true,即只要state发生改变,就会调用render()

PureComponent、memo

  • 如果通过props或state中数据是否改变,来决定shouldComponentUpdate返回true或false,比较麻烦
  • 所以React已经默认帮我们实现好了,将class继承自PureComponent
import React, { PureComponent } from "react";
import Home from "./Home";
import Recommend from "./Recommend";
import Profile from "./Profile";
export class App extends PureComponent {
  constructor() {
    super();
    this.state = {
      msg: "zm6",
      count: 99,
    };
  }

  //   如果不使用Component,App进行性能优化
  //   react-dom.development.js:86 Warning: App has a method called shouldComponentUpdate().
  //   shouldComponentUpdate should not be used when extending React.PureComponent. Please extend React.Component if shouldComponentUpdate is used.
  //   shouldComponentUpdate(newProp, newState) {
  //     if (this.state.msg !== newState.msg || this.state.count !== newState.count) {
  //       return true;
  //     }
  //     return false;
  //   }

  changeText() {
    this.setState({
      msg: "changed msg",
    });
  }
  add() {
    this.setState({
      count: this.state.count + 1,
    });
  }
  render() {
    console.log("AppRender=> ");
    const { msg, count } = this.state;
    return (
      <div>
        <h2>
          App-{msg}-{count}
        </h2>
        <button onClick={(e) => this.changeText()}>changeText</button>
        <button onClick={(e) => this.add()}>+1</button>
        <Home msg={msg} />
        <Recommend count={count} />
        <Profile msg={msg} />
      </div>
    );
  }
}
export default App;
import React, { PureComponent } from "react";
export class Home extends PureComponent {
  render() {
    console.log("HomeRender=>");
    return (
      <div>
        <h2>Home-{this.props.msg}</h2>
      </div>
    );
  }
}
export default Home;
import React, { PureComponent } from "react";
export class Recommend extends PureComponent {
  render() {
    console.log("RecommendRender=>");
    return (
      <div>
        <h2>Recommend-{this.props.count}</h2>
      </div>
    );
  }
}
export default Recommend;
import { memo } from "react";
const Profile = memo(function (props) {
  console.log("ProfileRender=> ");
  return <div>Profile-{props.msg}</div>;
});
export default Profile;

数据不可变的力量

import React, { PureComponent } from "react";

export class App extends PureComponent {
  constructor() {
    super();
    this.state = {
      books: [
        { name: "js高级程序设计", price: 1, count: 10 },
        { name: "js重难点", price: 2, count: 20 },
        { name: "你不知道的js", price: 4, count: 30 },
        { name: "Vue设计与实现", price: 7, count: 40 },
      ],
    };
  }
  addCount(index) {
    const books = [...this.state.books];
    books[index].count += 1;
    this.setState({
      books: books,
    });
  }
  addNewBook() {
    const newBook = { name: "Nest从入门到入土", price: 999, count: 1000 };

    // 1.直接修改原有的state,重新设置一遍
    // 在PureComponent是不能引起重新渲染的
    // this.state.books.push(newBook);
    // this.setState({
    //   books: this.state.books,
    // });

    // 2.浅拷贝一份books,在新的books中修改,设置新的books,因为是浅层比较,所以会重新渲染
    const books = [...this.state.books];
    books.push(newBook);
    this.setState({
      books,
    });
  }
  render() {
    const { books } = this.state;
    return (
      <div>
        <h2>data-list</h2>
        <ul>
          {books.map((item, index) => {
            return (
              <li key={index}>
                <span>
                  name:{item.name}-price:{item.price}-count:{item.count}
                </span>
                <button onClick={(e) => this.addCount(index)}>+1</button>
              </li>
            );
          })}
        </ul>
        <button onClick={(e) => this.addNewBook()}>add new book</button>
      </div>
    );
  }
}
export default App;