react-函数组件的性能优化突破口

react-函数组件的性能优化突破口

前言

react hooks的出现使得函数组件拥有类组件的能力,且函数组件不像类组件那样天生就有内置的一堆生命周期(不管你用不用,都有),所以相对而言,函数组件更轻量,代码书写上也更简洁。but,函数组件也不是完美的,它在每次渲染的时候都会重新调用一次,内部如果有复杂的计算,更新一次就会重新执行,十分耗时。

diff算法的缺点

函数组件和类组件在进行更新的时候都避不开diff算法,这是相当耗时的一个过程。比较渲染前后生成的两棵虚拟dom树的差异,更新差异,渲染成真实dom。diff算法不比直接操作dom快,甚至还可能慢,因为它多了一个比较过程,而直接操作dom不需要这个环节。

突破口

针对以上描述,可以理解为,react函数组件的性能优化突破口两个,避免重复计算,减少diff.,下面针对这两个点进行性能优化

React.memo

如果你用过React的类组件,应该知道shouldComponentUpdate 和 PureComponent,这两个都是为了减少diff用的,而React.memo就相当于这两个API在函数组件中的应用。举个例子,父组件更新会引发子组件更新,而父组件传递给子组件的props可能变也可能不变,变的时候子组件应该渲染,但不变的时候,自然不应该重新渲染。

index.js

import React, { useState } from "react";
import {render} from "react-dom";
import Child from './child'

function App() {
  const [rank, setRank] = useState("洞玄")

  console.log("App render")

  return (
    <div >
      <p>{ rank }</p>
      <button onClick={() => setRank("知命")}>点击破镜</button>
      <Child name="君陌"></Child>
    </div>
  );
}

render(<App />, document.getElementById("root"));


child.js


import React from "react";

function Child({ name }) {

  console.log("child render")

  return <p>{name}</p>
}

export default Child


分析

  • 页面首次渲染,父子组件挂载,App render和child render 都会被打印,这是正常的
    在这里插入图片描述
  • 点击按钮,父组件状态改变,App render和child render 都会被打印,前者正常,状态改变触发更新。但是对于子组件,传递的props是一个写死的字符串,按理来说不应该受到父组件更新的影响,所以这就是一个性能优化的点,减少不需要的diff。

在这里插入图片描述

memo闪亮登场

用memo把子组件包裹一下即可,这就相当于类组件PureComponent的用法


// child.js
import React,{memo} from "react";

function Child({ name }) {

  console.log("child render")

  return <p>{name}</p>
}

export default memo(Child)


  • 再点击试试,可以看到这次子组件没有再度刷新

在这里插入图片描述

  • memo还可以接收第二个参数,是一个可定制化的比较函数,其返回值与 shouldComponentUpdate的相反,shouldComponentUpdate返回true是更新,memo是返回false更新.默认情况下,memo只会浅比较一下引用,而如果需要精确比对复杂对象属性,就需要第二个参数来定制化处理

import React, { memo } from "react";

function Child({ name }) {

  console.log("child render")

  return <p>{name}</p>
}


function isEqual(prevProps, nextProps) {

  // prevProps.name===nextProps.name相等,返回true,不进行更新
  if (prevProps.name === nextProps.name) {
    return true
  }

 // prevProps.name===nextProps.name不相等,返回false,进行更新
    return false
  

}

export default memo(Child, isEqual)

useCallback

在最开始的时候有提到,函数组件每次刷新都会被重新调用一次,这也是性能优化的突破口,useCallback就是用来干这个事的

举个例子

  • 将上述示例做一些更改,将更改父组件状态的事件处理函数下放到子组件,观察父组件刷新对子组件的影响

//index.js
import React, { useState } from "react";
import { render } from "react-dom";
import Child from "./child";

function App() {
  const [rank, setRank] = useState("洞玄");

  console.log("render App");

  const handleClick = () => {
    setRank("知命");
  };

  return (
    <div >
      <p>{rank}</p>
      <Child handleClick={handleClick} name="君陌" />
    </div>
  );
}

render(<App />, document.getElementById("root"));


//child.js

import React,{memo} from "react";

function Child({ handleClick, name }) {
  console.log("render child");
  return (
    <div>
      <button onClick={handleClick}>点我破境</button>
      <p>{name}</p>
    </div>
  );
}

export default memo(Child);

  • 点击按钮,会发现这次子组件又渲染了一次,注意看,这次已经加了memo包裹

在这里插入图片描述

分析

默认memo包裹只会浅比较对象引用,父组件传递给子组件的props有两个,处理函数handleClick,和name 。name肯定是无论如何都不变的,这里就是个字符串常量,那就是handleClick变了,可这个貌似也没做什么更改,问题出在哪?

  • 我们用memo的第二个参数,打印一下
    export default memo(Child,(pre,next)=>{console.log(pre.handleClick===next.handleClick)});

在这里插入图片描述

  • 看,是false,所以子组件会更新,这就回到了原始问题上,函数组件每次刷新都会重新调用,而函数也是对象,每次引用不一样
  • 函数也是对象,每次创建可以简化为下边代码,引用不一样

在这里插入图片描述


  • 实际上,函数组件每次更新,调用前后的定义的函数,我们应该让它一样,自始至终保持一个相同引用,这就需要useCallback
  • useCallback使用和useEffect差不多,第二个参数是依赖数组,没有就传空数组,它看起来像这样
const callback = () => {
  fn(a, b);
}

const memoizedCallback = useCallback(callback, [a, b])
  • 我们用useCallback把要传递给子组件的函数包裹一下
import React, { useState ,useCallback} from "react";
import { render } from "react-dom";
import Child from "./child";

function App() {
  const [rank, setRank] = useState("洞玄");

  console.log("render App");

  const handleClick = () => {
    setRank("知命");
  };

  const handleUseCallback=useCallback(handleClick,[])

  return (
    <div >
      <p>{rank}</p>
      <Child handleClick={handleUseCallback} name="君陌" />
    </div>
  );
}

render(<App />, document.getElementById("root"));





在这里插入图片描述

useMemo

也许你好奇memo和useMemo的关系,其实没啥关系,要说有,就是都能起到性能优化的作用。前者是对整个函数组件而言的优化,后者是对某一段逻辑,或者说对某个内部实现复杂计算的函数的优化。函数组件每次刷新就会重新调用,使用useMemo可以使得相同的复杂计算只计算一次。useMemo的使用和useCallback很像,两个参数,函数,依赖数组。下面举个例子


import React, { useState } from "react";
import { render } from "react-dom";

function App() {
  const [num, setNum] = useState(0);


  function computed() {
    let sum = 0;

    for (let i = 0; i < 10000; i++) {
      sum += i;
    }

    console.log(sum)

    return sum ;
  }


  return (
    <div >
      <h1>num:{num}</h1>
      <button onClick={() => setNum(num + computed())}按我</button>
    </div>
  );
}

render(<App />, document.getElementById("root"))




这是一个简单的计数器,点击按钮,将运算结果累加,我们会发现每次点击都会打印数值,是因为每次点击状态改变触发更新,而函数组件每次更新都会重新调用,这也就意味着内部的逻辑都会执行一次,每次都重新计算是不必要的,尤其是计算量大的时候,应该缓存数据

在这里插入图片描述

useMemo缓存

import React, { useState,useMemo } from "react";
import { render } from "react-dom";

function App() {
  const [num, setNum] = useState(0);


  function computed() {
    let sum = 0;

    for (let i = 0; i < 10000; i++) {
      sum += i;
    }

    console.log(sum)

    return sum ;
  }

  const handleUseMemo=useMemo(computed,[])

  return (
    <div >
      <h1>num:{num}</h1>
      <button onClick={() => setNum(num + handleUseMemo)}>按我</button>
    </div>
  );
}

render(<App />, document.getElementById("root"))

在这里插入图片描述

发布了453 篇原创文章 · 获赞 790 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/qq_42813491/article/details/104000338