🌈 React 函数式组件优化

1. React 性能优化思路

  1. 减少重新 render 的次数。
  2. 减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。
  • 在使用类组件(class)的时候,使用的 React 优化 API 主要是:shouldComponentUpdatePureComponent,都是为了减少重新 render 的次数,主要是减少父组件更新而子组件也更新的情况。

2. 函数组件优化方法

2.1 React.memo (减少 render 的次数)

可以减少重新 render 的次数,对标类组件里面的 PureComponent

举个简单的🌰 :

  • 修改父组件 title 的时候同时传递给子组件一个 name 值。
// 父组件
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Child from './child'

function Father() {
  const [title, setTitle] = useState("父组件的title");

  return (
    <div className="Father">
      <h1>{ title }</h1>
      <button onClick={() => setTitle("父组件的title改变了")}>修改父组件的title</button>
      <Child name="父组件传递给子组件的值"></Child>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Father />, rootElement);
// 子组件
import React from "react";

function Child(props) {
  console.log(props.name)
  return <p>{props.name}</p>
}

export default Child;
  • 首次渲染的效果如下:

  • 并且在控制台会打印 "父组件传递给子组件的值",说明 Child 组件渲染了。

  • 接下来点击 修改父组件的title 这个button,页面会变成:

  • 可见 父组件的title 已经改变了,而且控制台再次打印了 "父组件传递给子组件的值"。子组件在 props 没有改变的情况下,再次渲染了。假设子组件如果非常庞大,渲染一次会消耗很多的性能,我们应该尽量减少这个组件的渲染,否则就容易产生性能问题。

React.memo 在给定相同 props 的情况下渲染相同的结果,并且通过记忆组件渲染结果的方式来提高组件的性能表现。那么上面的例子就可以做如下修改:

// 子组件
import React from "react";

function Child(props) {
  console.log(props.name)
  return <p>{props.name}</p>
}

export default React.memo(Child); // 用 React.memo()包裹

2.2 useCallback (减少 render 的次数)

根据上面的例子,再改一下需求,父组件新增一个副标题和一个修改副标题的button,然后把修改标题的button放到子组件里。

// 父组件
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Child from "./child";

function Father() {
  const [title, setTitle] = useState("父组件的title");
  const [subtitle, setSubtitle] = useState("父组件的副title");

  const callback = () => {
    setTitle("父组件的title改变了");
  };
  return (
    <div className="Father">
      <h1>{title}</h1>
      <h2>{subtitle}</h2>
      <button onClick={() => setSubtitle("父组件的副title改变了")}>修改父组件的副title</button>
      <Child onClick={callback} name="父组件传递给子组件的值" />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Father />, rootElement);
// 子组件
import React from "react";

function Child(props) {
  console.log(props.name);
  return (
    <div>
      <button onClick={props.onClick}>修改父组件的title</button>
      <h1>{props.name}</h1>
    </div>
  );
}

export default React.memo(Child);
  • 首次渲染的效果如下:

  • 并且在控制台会打印 "父组件传递给子组件的值",说明 Child 组件渲染了。

  • 接下来点击 修改父组件的副title 这个button,页面会变成:

  • 可见 父组件的副title 已经改变了,而且控制台再次打印了 "父组件传递给子组件的值"。子组件在 props 没有改变的情况下,再次渲染了。但是子组件没有任何变化,那么这次子组件的重新渲染就是多余的,为什么又重新渲染了呢?

一个组件重新渲染,一般有3种情况:

  1. 组件自己的状态改变;
  2. 父组件重新渲染,导致子组件重新渲染,但是父组件的 props 没有改变;
  3. 父组件重新渲染,导致子组件重新渲染,但是父组件传递的 props 改变了;

第一种果断排除,当点击修改副title的时候并没有去改变子组件的状态;

第二种想一下也应该排除,父组件重新渲染了,父组件传递给子组件的 props 没有改变,但是子组件重新渲染了,我们这个时候用 React.memo 来解决了这个问题;

那么应该是第三种情况了,当父组件重新渲染的时候,传递给子组件的 props 发生了改变,传递给子组件的就两个属性,一个是 name,另一个是 onClick ,name 传递的是常量,没有变,变的就是 onClick 了,为什么传递给 onClick 的 callback 函数会发生改变呢?在文章的开头就已经说过了,在函数式组件里每次重新渲染,函数组件都会重头开始重新执行,那么这两次创建的 callback 函数肯定发生了改变,所以导致了子组件重新渲染。

useCallback 在函数没有改变的时候,重新渲染的时候保持两个函数的引用一致,那么上面的例子就可以做如下修改:

// 父组件
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import Child from "./child";

function Father() {
  const [title, setTitle] = useState("父组件的title");
  const [subtitle, setSubtitle] = useState("父组件的副title");

  const callback = () => {
    setTitle("父组件的title改变了");
  };

  // 通过 useCallback 进行记忆 callback,并将记忆的 callback 传递给子组件
  const _callback = useCallback(callback, []);

  return (
    <div className="Father">
      <h1>{title}</h1>
      <h2>{subtitle}</h2>
      <button onClick={() => setSubtitle("父组件的副title改变了")}>修改父组件的副title</button>
      <Child onClick={_callback} name="父组件传递给子组件的值" />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Father />, rootElement);

2.3 useMemo (减少计算的量)

useMemo 主要是用来缓存计算量比较大的函数结果,可以避免不必要的重复计算,和 Vue 里面的 computed 有异曲同工的作用,可以减少计算的量。

import React, { useState } from "react";
function App() {
  const [num, setNum] = useState(0);

  // 一个非常耗时的一个计算函数
  // result 最后返回的值是 49995000
  function resultFn() {
    let result = 0;

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

    console.log(result) // 49995000
    return result;
  }

  const resultNum = resultFn();

  return (
    <div className="App">
      <h1>count:{num}</h1>
      <button onClick={() => setNum(num + resultNum)}>+1</button>
    </div>
  );
}

如果我们把 i 变成10000000,每次点击 +1 按钮的时候,都会重新渲染且得到的结果都是一样的,这样会对性能造成一些影响,我们可以做如下修改:

import React, { useState, useMemo } from "react";
function App() {
  const [num, setNum] = useState(0);

  // 一个非常耗时的一个计算函数
  // result 最后返回的值是 49995000
  function resultFn() {
    let result = 0;

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

    console.log(result) // 49995000
    return result;
  }

  const resultNum = useMemo(resultFn, []); //这使用了 useMemo

  return (
    <div className="App">
      <h1>count:{num}</h1>
      <button onClick={() => setNum(num + resultNum)}>+1</button>
    </div>
  );
}

需要注意两点:

一、如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值;

二、如果计算量很小的计算函数,可以选择不使用 useMemo,避免使用不当造成其他问题;

更多技术分享请关注我的个人博客 www.chengxiaohui.cn。欢迎骚扰━(`∀´)ノ亻!

原文链接:juejin.im

上一篇:聊聊Javascript里的深浅拷贝
下一篇:Dart 也想要甜甜的语法糖

相关推荐

  • 🚩Vue源码——组件是如何注册的

    最近参加了很多场面试,几乎每场面试中都会问到Vue源码方面的问题。在此开一个系列的专栏,来总结一下这方面的经验,如果觉得对您有帮助的,不妨点个赞支持一下呗。 前言 在上一篇 🚩Vue源码——组件...

    23 天前
  • 🚩Vue源码——组件如何渲染成最终的DOM

    最近参加了很多场面试,几乎每场面试中都会问到Vue源码方面的问题。在此开一个系列的专栏,来总结一下这方面的经验,如果觉得对您有帮助的,不妨点个赞支持一下呗。 前言 Vue有两个核心思想,一个是数据...

    1 个月前
  • 🍊仿Element自定义Vue组件库

    前言 🍊 市面上目前已有各种各样的UI组件库,他们的强大毋庸置疑。但是有时候我们有必要开发一套属于自己团队的定制化组件库。还有时候原有的组件不能满足我们的各种需求,就需要在原有的组件上进行改造...

    3 个月前
  • 🆘 一次理解清楚,为什么使用 React useEffect 中使用 setInterval 获取的值不是最新的

    Intro 这篇文章将通过一个使用 React Hook 常遇到的问题(stale state)入手,尝试理解 Hook 的内部运行逻辑。 废话不多说,直接看示例Sandbox。

    3 个月前
  • (vue框架)为element组件赋初始值以后无法更改值得问题

    情况描述:组件未加载时已有初始值,mounted里面加载数据,赋值,渲染以后,组件无法更改内容 data里面已经有这个表单对象的初始值但还是无法修改,之前有过一次,没有给表单绑定对象,所以赋值以后无法...

    1 年前
  • 魔方实时通信一对一音视频组件

    魔方实时通信/协作引擎(Web SDK)是一个全能力的实时云端协作引擎 魔方实时通信,请点击这个 继上一个im聊天组件增加了发动语音,语音视频通话功能 项目的源代码在这里 在线演示 项目结构如下: ...

    2 年前
  • 高频数据交换下Flutter与ReactNative的对比

    (标题图片来自网络,侵删) 后端使用go写的socketio服务模拟期货行情数据,每10ms推送10条行情数据 ReactNative已经尽力优化了。 Flutter由于没flutter-socket...

    2 年前
  • 高阶组件之属性代理

    新组件类继承子React.component类,对传入的组件进行一系列操作,从而产生一个新的组件,达到增强组件的作用 操作props 访问ref 抽取state 封装组件 废话不多说,直接上代码:...

    2 年前
  • 高阶组件HOC - 小试牛刀

    1. 前言 老毕曾经有过一句名言,叫作“国庆七天乐,Coding最快乐~”。所以在这漫漫七天长假,手痒了怎么办?于是乎,就有了接下来的内容。。。 2. 一个中心 今天要分享的内容有关高阶组件的使用。

    2 年前
  • 高阶组件 + New Context API = ?

    1. 前言 继上次小试牛刀尝到高价组件的甜头之后,现已深陷其中无法自拔。。。那么这次又会带来什么呢?今天,我们就来看看【高阶组件】和【New Context API】能擦出什么火花! 2. New C...

    2 年前

官方社区

扫码加入 JavaScript 社区