发布于 2026-01-06 8 阅读
0

React 运行缓慢,现在该怎么办?suprsend-go

React运行缓慢,现在该怎么办?

停止

查看更多文章:

  1. 使用gRPC和微服务构建可扩展的通知系统
  2. 在 React 网站中添加通知源
  3. 2023 年现代应用程序通知基础架构完整指南

应用程序的性能瓶颈通常可以分为两类:

  1. I/O 密集型:这类应用程序的大部分时间都用于处理输入和输出。
  2. CPU密集型:这类应用程序的大部分时间都用于执行计算任务。

那么,这些分类如何应用于前端应用程序,特别是 React 应用程序呢?

React 中的 I/O 性能挑战

在 React 应用中,I/O 性能问题经常出现,主要与异步 HTTP 调用有关。如果网络请求管理不善,会导致应用运行缓慢。本文主要关注 CPU 性能,但也会简要提及一些可以解决 I/O 密集型问题的关键领域:

  • 尽可能实现延迟加载。
  • 在初始加载资产和后端请求时要格外小心。
  • 减少加载高度静态元素(例如选择选项、配置)的频率。
  • 消除特定请求次数的抖动。
  • 尽可能使用 Promise.all 等技术并行处理请求。
  • 通过优化数据库访问等措施,提高关键后端端点的效率。

React 中的 CPU 性能挑战

本文主要探讨 React 中的 CPU 性能挑战。在深入探讨细节之前,我们先来明确一下性能的定义:

  • 浏览器应用程序主要以单线程程序的形式运行。
  • 脚本任务,例如 JavaScript 执行、DOM 渲染和事件处理,都在同一个线程中执行。
  • 运行缓慢的 JavaScript 模块可能会阻塞主线程。
  • 如果主线程被阻塞,用户界面将无响应,导致每秒帧数 (fps) 下降。
  • 响应式用户界面以至少 30 fps 为目标,理想情况下达到 60 fps,这意味着每一帧的计算时间应在 30 毫秒或更短时间内完成。

在 React 的背景下,这个问题变得尤为关键。当触发 React 组件更新时,整个子树必须在 30 毫秒内渲染完成。对于复杂且冗长的组件结构(例如表格、树和列表),这尤其具有挑战性,因为这些结构可能需要大规模的重新渲染。

React渲染和提交阶段

从宏观层面来看,React 的运行分为两个截然不同的阶段:

渲染阶段:

  • 当组件更新时启动,由 props 或 hooks 的变化触发。
  • React 会遍历组件子树,渲染每个子组件并计算虚拟 DOM (VDOM) 子树。
  • 只有受更新影响的“脏”子树需要重新计算;已更新组件的父组件可能不需要重新渲染。
  • 该阶段的效率与每个子组件的大小和计算成本成正比。
  • React.memo 可用于为更高效的渲染过程提供提示。

提交阶段:

  • 渲染阶段会生成整个用户界面的全新虚拟 DOM。
  • 在提交阶段,React 会将新树与之前的树进行比较(VDOM diffing)。
  • React 会计算反映新的虚拟 DOM 树所需的最小 DOM 变更次数。
  • DOM 变更生效,用户界面随之更新。
  • 这一阶段本身就具有很高的效率。
  • 整个过程必须在 30 毫秒或 16 毫秒内完成(分别对应 30 帧/秒和 60 帧/秒),用户界面才能被视为响应迅速。工作负载与应用程序的大小成正比。

接下来的研究将着重于提升渲染阶段的效率。在深入探讨优化技术之前,至关重要的是要了解如何衡量和识别应用程序中的瓶颈组件。

测量

我经常使用的工具包括:

  1. Chrome 开发者工具的性能选项卡
  2. React 开发工具的性能选项卡

Chrome 开发者工具的性能选项卡

这款工具功能全面,适用于任何浏览器应用程序,堪称佼佼者。它能提供帧速率分析、捕获堆栈跟踪、识别代码中的慢速或高性能部分等等。其主要用户界面以火焰图的形式呈现。

要深入了解 Chrome 的性能选项卡在 React 中的应用,请参阅此文档

React 开发工具的性能选项卡

要使用此工具,您需要在浏览器中安装 React Dev Tool 扩展程序。它会将 Chrome 开发者工具“性能”选项卡中的信息专门针对 React 进行定制。通过火焰图,您可以观察不同的提交阶段以及在各个渲染阶段执行的 JavaScript 代码。

该工具可帮助轻松确定:

  • 当组件重新渲染时。
  • 哪些道具发生了变化?
  • 哪些钩子发生了变化,包括状态、上下文等等。更多详情请参阅介绍性文章

测量方法

以下是我评估前端应用程序时偏好的方法:

  1. 找出问题所在:

    • 找出导致用户界面响应问题的页面交互原因。
  2. 提出假设:

    • (可选)提出关于问题可能发生地点的想法。
  3. 措施:

    • 通过测量帧速率(fps)等关键指标来验证问题。
  4. 衡量(第二部分):

    • 找出代码中的问题部分;(可选)验证你的假设。
  5. 创建解决方案:

    • 根据收集到的信息实施解决方案。
  6. 测量溶液:

    • 通过检查关键指标来验证所实施的解决方案是否解决了问题或缓解了问题。

缺乏适当的衡量指标,优化工作实际上会收效甚微。虽然有些问题显而易见,但大多数问题都需要进行彻底的衡量,而这正是绩效提升过程的基石。

此外,通过衡量指标,您可以向上级汇报成果,以百分比增益的形式向用户、利益相关者和领导层通报应用程序特定领域取得的性能改进。

React 应用中 CPU 密集型问题的通用解决方案

现在,我们已经掌握了测量数据并了解了问题所在,接下来让我们深入探讨可能的解决方案。优化 React 性能的关键在于改进组件的渲染方式和渲染的组件类型。

许多性能问题也源于反模式。消除这些反模式,例如避免在渲染方法中使用内联函数定义,有助于提高渲染效率。事实上,解决不良模式可以同时降低复杂性并提升性能。

🤔 改进组件渲染

在我们的 React 应用中识别运行缓慢的组件,通常会指向那些渲染困难或单个页面上实例过多的特定组件。导致它们运行缓慢的原因有很多:

  • 阻止组件内的计算。
  • 渲染大型组件树。
  • 使用昂贵或低效的库。

这些问题大多可以归结为提高组件渲染速度。有时,关键组件不能依赖过于复杂的库,这就需要回归基本原理,并实现更简单的替代方案。

例如,我在一个复杂表格中,每行多个单元格都过度使用 Formik 组件时,就遇到了这类挑战。虽然提高单个组件的效率大有裨益,但最终我们必须关注哪些组件正在渲染。

🧙 改进渲染组件

这方面可以从两个方面进行改进:

  1. 虚拟化:

    • 仅渲染视口中可见的组件。例如,仅渲染用户可见的表格行或列表项。这种方法对于复杂的 UI 非常有效,虽然可以不考虑“渲染什么”这一步骤就应用,但建议这样做。现代库通常提供强大的表格和列表虚拟化支持,例如 `<React>` react-virtualized。虚拟化可以减少 React 在给定帧中需要渲染的组件数量。
  2. 道具优化:

    • React 的目标是使组件类似于纯函数,但可能会尝试渲染比必要次数更多的次。

React.memo:

  • React 中的大多数组件都可以进行记忆化,确保在 props 相同的情况下,组件返回相同的组件树(尽管 hooks、state 和 context 仍然有效)。利用记忆化机制,ReactReact.memo会告知 React,如果组件的 props 保持不变,则跳过重新渲染这些已记忆化的组件。

      import React from 'react';
    
      const MyComponent = React.memo((props) => {
        // Component logic here
      });
    
      export default MyComponent;
    

伪造的属性更改:useCallback:

  • 解决伪造属性变更的问题涉及属性内容保持不变但引用发生变化的情况。一个典型的例子是事件处理程序。

      import React, { useCallback } from 'react';
    
      const MyComponent = () => {
    

const onChange = useCallback((e) => console.log(e), []);

    return <input onChange={onChange} />;
  };

  export default MyComponent;
  ```
Enter fullscreen mode Exit fullscreen mode

虚假道具变更:useMemo:

  • 当构建复杂的数据结构时,如果没有在将其作为 props 传递之前进行适当的记忆化处理,就会出现类似的挑战。利用记忆化处理useMemo可以确保仅在依赖关系发生变化时才重新计算行,从而提高效率。

      import React, { useMemo } from 'react';
    
      const MyComponent = ({ data, deps }) => {
        const rows = useMemo(() => data.filter(bySearchCriteria).sort(bySortOrder), [deps]);
    
        return <Table data={rows} />;
      };
    
      export default MyComponent;
    

虽然您可以灵活地自定义React.memo当前道具与先前道具的比较方式,但保持计算速度至关重要,因为它是渲染阶段不可或缺的一部分。避免在每次渲染期间进行过于复杂的深度比较。

现在看起来怎么样?

道具已更换

在 React 开发工具中的显示效果:

图片描述

真的吗?这些是道具更换吗?使用useCallbackuseMemo

父渲染

在 React 开发工具中的显示效果:

图片描述

用于React.memo记忆纯成分。

钩子已更改(状态、上下文)

在 React 开发工具中的显示效果:

图片描述

这里没有什么显而易见的解决方法。尝试验证一下更改的钩子是否合理。或许是错误的上下文提供程序以类似于伪造属性更改的方式,伪造了更改。


类似地,我个人在 Slack 上运营着一个由开发者主导的社区。我们在那里讨论这类实现、集成、一些“真相炸弹”、奇奇怪怪的聊天、线上会议,以及所有能帮助开发者保持理智的事情 ;) 毕竟,知识太多也可能很危险。

我诚挚邀请您加入我们的免费社区,参与讨论,分享您的宝贵经验和专业知识。您可以填写此表格,几天后您将收到一封 Slack 邀请邮件。我们社区汇聚了来自众多知名公司(Atlassian、Scaler、Cisco、IBM 等)的优秀成员,您一定不想错过与他们交流的机会。邀请表格


您不妨了解一下如何以无缝的方式集成您的通知基础设施。

GitHub 标志 suprsend / suprsend-go

SuprSend Go SDK

停止

SuprSend Go SDK

安装

go get github.com/suprsend/suprsend-go
Enter fullscreen mode Exit fullscreen mode

用法

初始化 SuprSend SDK

import (
    "log"

    suprsend "github.com/suprsend/suprsend-go"
)

func main() {
    opts := []suprsend.ClientOption{
        // suprsend.WithDebug(true),
    }
    suprClient, err := suprsend.NewClient("__api_key__", "__api_secret__", opts...)
    if err != nil {
        log.Println(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

触发工作流

package main
import (
    "log"

    suprsend "github.com/suprsend/suprsend-go"
)

func main() {
    // Instantiate Client
    suprClient, err := suprsend.NewClient("__api_key__", "__api_secret__")
    if err != nil {
        log.Println(err)
        return
    }
    // Create WorkflowTriggerRequest body
    wfReqBody := map[string]interface{}{
        "workflow": "workflow-slug",
        "recipients": []map[string]interface{}{
            {
                "distinct_id": "0f988f74-6982-41c5-8752-facb6911fb08",
                // if $channels is present, communication will be tried on mentioned channels only (for this request).
                // "$channels": []string{"email"},
Enter fullscreen mode Exit fullscreen mode
文章来源:https://dev.to/nikl/react-is-slow-what-to-do-now-369g