分享

React 性能优化技巧总结

 星辰010 2019-11-04

代发帖子

本文将从 render 函数的角度总结 React App 的优化技巧。需要提醒的是,文中将涉及 React 16.8.2 版本的内容(也即 Hooks),因此请至少了解 useState 以保证食用效果。

正文开始。

当我们讨论 React App 的性能问题时,组件的 渲染 速度是一个重要问题。在进入到具体优化建议之前,我们先要理解以下 3 点:

这里 。

当我们在说「render」时,我们在说什么?

这个问题其实写过 React 的人都会知道,这里再简单说下:

在 class 组件中,我们指的是 render 方法:

  1. class Foo extends React.Component {  
  2.  render() {  
  3.    return 

     Foo 

    ;  
  4.  }  
  5. }  

在函数式组件中,我们指的是函数组件本身:

  1. function Foo() {  
  2.   return 

     Foo 

    ;  
  3. }  

什么时候会执行「render」?

render 函数会在两种场景下被调用:

1. 状态更新时

a. 继承自 React.Component 的 class 组件更新状态时

  1. import React from "react";  
  2. import ReactDOM from "react-dom";  
  3.   
  4. class App extends React.Component {  
  5.   render() {  
  6.     return ;  
  7.   }  
  8. }  
  9.   
  10. class Foo extends React.Component {  
  11.   state = { count: 0 };  
  12.   
  13.   increment = () => {  
  14.     const { count } = this.state;  
  15.   
  16.     const newCount = count < 10 ? count + 1 : count;  
  17.   
  18.     this.setState({ count: newCount });  
  19.   };  
  20.   
  21.   render() {  
  22.     const { count } = this.state;  
  23.     console.log("Foo render");  
  24.   
  25.     return (  
  26.       
      
  27.         

     {count} 

      
  28.         this.increment}>Increment  
  29.       
  
  •     );  
  •   }  
  • }  
  •   
  • const rootElement = document.getElementById("root");  
  • ReactDOM.render(, rootElement);  
  • 可以看到,代码中的逻辑是我们点击就会更新 count,到 10 以后,就会维持在 10。增加一个 console.log,这样我们就可以知道 render 是否被调用了。从执行结果可以知道,即使 count 到了 10 以上,render 仍然会被调用。

    总结:继承了 React.Component 的 class 组件,即使状态没变化,只要调用了setState 就会触发 render。

    b. 函数式组件更新状态时

    我们用函数实现相同的组件,当然因为要有状态,我们用上了 useState hook:

    1. import React, { useState } from "react";  
    2. import ReactDOM from "react-dom";  
    3.   
    4. class App extends React.Component {  
    5.   render() {  
    6.     return ;  
    7.   }  
    8. }  
    9.   
    10. function Foo() {  
    11.   const [count, setCount] = useState(0);  
    12.   
    13.   function increment() {  
    14.     const newCount = count < 10 ? count + 1 : count;  
    15.     setCount(newCount);  
    16.   }  
    17.   
    18.   console.log("Foo render");  
    19.     
    20.   return (  
    21.     
        
    22.       

       {count} 

        
    23.       Increment  
    24.     
      
  •   );  
  • }  
  •   
  • const rootElement = document.getElementById("root");  
  • ReactDOM.render(, rootElement);  
  • 我们可以注意到,当状态值不再改变之后,render 的调用就停止了。

    总结:对函数式组件来说,状态值改变时才会触发 render 函数的调用。

    2. 父容器重新渲染时

    1. import React from "react";  
    2. import ReactDOM from "react-dom";  
    3.   
    4. class App extends React.Component {  
    5.   state = { name: "App" };  
    6.   render() {  
    7.     return (  
    8.       "App">  
    9.           
    10.          this.setState({ name: "App" })}>  
    11.           Change name  
    12.           
    13.       
      
  •     );  
  •   }  
  • }  
  •   
  • function Foo() {  
  •   console.log("Foo render");  
  •   
  •   return (  
  •     
      
  •       

     Foo 

      
  •     
  •   
  •   );  
  • }  
  •   
  • const rootElement = document.getElementById("root");  
  • ReactDOM.render(, rootElement);  
  • 只要点击了 App 组件内的 Change name 按钮,就会重新 render。而且可以注意到,不管 Foo 具体实现是什么,Foo 都会被重新渲染。

    总结:无论组件是继承自 React.Component 的 class 组件还是函数式组件,一旦父容器重新 render,组件的 render 都会再次被调用。

    在「render」过程中会发生什么?

    只要 render 函数被调用,就会有两个步骤按顺序执行。这两个步骤非常重要,理解了它们才好知道如何去优化 React App。

    Diffing

    在此步骤中,React 将新调用的 render 函数返回的树与旧版本的树进行比较,这一步是 React 决定如何更新 DOM 的必要步骤。虽然 React 使用高度优化的算法执行此步骤,但仍然有一定的性能开销。

    Reconciliation

    基于 diffing 的结果,React 更新 DOM 树。这一步因为需要卸载和挂载 DOM 节点同样存在许多性能开销。

    参考文档 。

    1. const Bar = React.memo(  
    2.   function Bar({ name: { first, last } }) {  
    3.     console.log("update");  
    4.     return (  
    5.       

        

    6.         {first} {last}  
    7.         
    8.     );  
    9.   },  
    10.   (prevProps, newProps) =>  
    11.     prevProps.name.first === newProps.name.first &&  
    12.     prevProps.name.last === newProps.name.last  
    13. );  

    尽管这条建议是可行的,但我们仍要注意 比较函数的性能开销 。如果 props 对象过深,反而会消耗不少的性能。

    总结

    上述场景仍不够全面,但多少能带来一些启发性思考。当然在性能方面,我们还有许多其他的问题需要考虑,但遵守上述的准则仍能带来相当不错的性能提升。

      本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
      转藏 分享 献花(0

      0条评论

      发表

      请遵守用户 评论公约

      类似文章 更多