当前位置: 首页 > article >正文

React 性能优化

React 是一个用于构建动态用户界面的强大库,但随着应用程序的增长,可能会出现性能问题。在本指南中,我们将探讨优化 React Web 应用程序的关键步骤,以确保其平稳运行。

1、使用 React 的内置性能工具

React 提供了几个工具来帮助您识别和解决性能瓶颈:

  • React 开发者工具:

使用 Profiler 选项卡来测量组件的渲染情况,并识别不必要的渲染。

  • React 严格模式:

启用严格模式,以便在开发过程中捕获潜在的性能和编码问题。

提示:将您的应用程序包裹在 React.StrictMode 中,以显示有关低效模式的警告。

import Reactfrom'react';
importReactDOMfrom'react-dom/client';
importAppfrom'./App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
    <App />
  </React.StrictMode>
);

2、优化组件渲染

不必要的重新渲染会极大地减慢您的应用程序速度。您可以通过以下方式优化组件渲染:

  • 使用 React.memo:当函数组件的属性未更改时,防止其重新渲染。

    React 中父组件每次更新都会导致子组件重新渲染,即使子组件的状态没有发生变化。

    为了减少重复渲染,我们可以使用 React.memo来缓存组件,这样只有在传入组件的状态值发生变化时才会从新渲染。如果传入的值相同,则会返回缓存的组件。

import React, { memo } from'react';

const MyComponent = ({ data }) => {
  console.log('Rendering MyComponent');
  return <div>{data}</div>;
};

export default memo(MyComponent);
  • 使用 useCallback 和 useMemo
    使用 useCallback 来记忆函数;
    使用 useMemo 来记忆计算开销较大的操作。
import React, { useCallback, useMemo } from'react';

constApp = ({ numbers }) => {
const calculateSum = useMemo(() => numbers.reduce((a, b) => a + b, 0), [numbers]);
const handleClick = useCallback(() =>console.log('Clicked!'), []);

return (
    <div>
      <h1>Sum: {calculateSum}</h1>
      <button onClick={handleClick}>Click Me</button>
    </div>
  );
};

3、使用 React.lazy 进行代码拆分

使用 React.lazy 和 Suspense 将您的应用程序拆分为较小的包,以减少初始加载时间。

import React, { Suspense } from'react';

constHeavyComponent = React.lazy(() =>import('./HeavyComponent'));

constApp = () => {
return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
};

exportdefaultApp;

4、避免使用 内联对象

使用内联对象时,react会在每次渲染时重新创建对此对象的引用,这会导致接收此对象的组件将其视为不同的对象。因此,该组件对于props的千层比较始终返回false,导致组件一直渲染。

// Don't do this!
function Component(props) {
  const aProp = { someProp: 'someValue' }
  return <AComponent style={{ margin: 0 }} aProp={aProp} />  
}

// Do this instead :)
const styles = { margin: 0 };
function Component(props) {
  const aProp = { someProp: 'someValue' }
  return <AComponent style={styles} {...aProp} />  
}

5、避免内联函数和匿名函数

内联函数在每次渲染时都会创建新的引用,这可能会导致不必要的重新渲染。相反,在渲染逻辑之外定义函数或使用 useCallback 。

不良实践:

<button onClick={() => console.log('Clicked!')}>Click Me</button>

良好实践:

const handleClick = () => console.log('Clicked!');
<button onClick={handleClick}>Click Me</button>;

6、使用React.PureComponent , shouldComponentUpdate

父组件状态的每次更新,都会导致子组件的重新渲染,即使是传入相同props。但是这里的重新渲染不是说会更新DOM,而是每次都会调用diif算法来判断是否需要更新DOM。这对于大型组件例如组件树来说是非常消耗性能的。
在这里我们就可以使用React.PureComponent , shouldComponentUpdate生命周期来确保只有当组件props状态改变时才会重新渲染

PureComponent 是对类组件的 Props 和 State 进行浅比较;React.memo 是对函数组件的 Props 进行浅比较。

7、使用React.Fragment避免添加额外的DOM

	function Component() {
        return (
            <React.Fragment>
                <h1>Hello world!</h1>
                <h1>Hello there!</h1>
                <h1>Hello there again!</h1>
            </React.Fragment>
        )
    }    

8、减少状态和属性穿透

避免过度的状态更新和不必要的属性穿透:

使用 Context API 或像 Redux 或 Zustand 这样的状态管理库来管理全局状态。

将组件拆分为更小、更专注的组件,这些组件管理自己的状态。

9、优化图像和资源

大型图像和资源可能会减慢您的应用程序速度。以下是优化它们的方法:

使用像 ImageOptim 或 TinyPNG 这样的工具来压缩图像。

使用带有 srcset 属性的响应式图像或像 react-image 这样的库。

使用像 react-lazyload 这样的库来懒加载图像。

10、最小化依赖项

审核您的 package.json 以查找不必要的依赖项。大型依赖包可能会减慢您的应用程序速度。

使用像 Bundlephobia 这样的工具来分析依赖项的大小。

优先选择轻量级的替代方案或自定义解决方案。

额外奖励

使用性能监控工具

利用像 LighthouseSentry 或 Datadog 这样的工具来监控和调试生产中的性能问题。

结论

总览:react的优化核心思想就是让react跳过重新渲染那个些没有改变的Component,而只重新渲染发生变化的Component。

优化您的 React Web 应用程序是一个持续的过程。首先使用 React 工具识别性能瓶颈,然后逐步应用这些技术。通过遵循这些步骤,您将提供更快、更高效的用户体验。

二、craco/webpack打包优化

1.懒加载图片、css、js,路由组件、antd组件按需加载
2.第三方的包,使用cdn,不要打包进来
3.拆分js
4.避免重复打包依赖
5.开启GZIP压缩

  1. 安装craco:

    npm install @craco/craco
  2. 修改package.json中的脚本部分,使用craco代替react-scripts:

    "scripts": {
      "start": "craco start",
      "build": "craco build",
      "test": "craco test",
      "eject": "react-scripts eject"
    }
  3. 在项目根目录下创建一个craco.config.js文件,用于自定义配置:

const FileManagerPlugin = require("filemanager-webpack-plugin");
const WebpackBar = require("webpackbar");
const { DllReferencePlugin } = require("webpack");
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const TerserPlugin = require("terser-webpack-plugin");
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const { addBeforeLoaders, removeLoaders, loaderByName } = require("@craco/craco");
const fs = require("fs");
const path = require("path");
const { name, version } = require("./package.json");
const manifest = require("./public/lib/vendor.json");

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
const appPath = resolveApp(".");
const appBuild = resolveApp("build");

const smp = new SpeedMeasurePlugin();

process.env.PORT = 3000;
// 环境信息
const env = process.env.REACT_APP_TARGET_ENV;

let source = `${appPath}/config.dev.js`;
if (env === "test") {
  source = `${appPath}/config.test.js`;
} else if (env === "pre") {
  source = `${appPath}/config.pre.js`;
} else if (env === "pro") {
  source = `${appPath}/config.pro.js`;
} 

module.exports = {
  reactScriptsVersion: "react-scripts" /* (default value) */,
  babel: {
    plugins: [
      // lodash按需加载
      "lodash",
    ],
    loaderOptions: {
      // babel-loader开启缓存
      cacheDirectory: true,
    },
  },
  plugins: [
    {
      plugin: {
        overrideDevServerConfig: ({ devServerConfig }) => {
          return {
            ...devServerConfig,
            headers: {
              "Access-Control-Allow-Origin": "*",
            },
          };
        },
        overrideWebpackConfig: ({ webpackConfig, context: { env } }) => {
          if (env !== "development") {
            // 缩小生产环境所有loaders的检索范围
            webpackConfig.module.rules[0].oneOf.forEach((rule) => {
              rule.include = path.resolve(__dirname, "src");
            });
          } else {
            // 缩小本地开发环境所有loaders的检索范围
            webpackConfig.module.rules[0].include = path.resolve(__dirname, "src");
            webpackConfig.module.rules[1].oneOf.forEach((rule, index) => {
              rule.include = path.resolve(__dirname, "src");
              // 本地开发环境babel-loader比较耗时,故加上thread-loader
              if (index === 3) {
                const babelLoader = {
                  loader: rule.loader,
                  options: rule.options,
                };
                rule.use = ["thread-loader", babelLoader];
                delete rule.loader;
                delete rule.options;
              }
            });
          }
          return {
            ...webpackConfig,
          };
        },
      },
    },
  ],
  webpack: smp.wrap({
    alias: {
      "@": path.resolve(__dirname, "src"),
      "@components": path.resolve(__dirname, "src/components"),
      "@containers": path.resolve(__dirname, "src/containers"),
      "@constants": path.resolve(__dirname, "src/constants"),
      "@utils": path.resolve(__dirname, "src/utils"),
      "@routes": path.resolve(__dirname, "src/routes"),
      "@assets": path.resolve(__dirname, "src/assets"),
      "@styles": path.resolve(__dirname, "src/styles"),
      "@services": path.resolve(__dirname, "src/services"),
      "@mocks": path.resolve(__dirname, "src/mocks"),
      "@hooks": path.resolve(__dirname, "src/hooks"),
      "@stories": path.resolve(__dirname, "src/stories"),
    },
    // configure: { /* Any webpack configuration options: https://webpack.js.org/configuration */ },
    configure: (webpackConfig, { env }) => {
      // 配置扩展扩展名优化
      webpackConfig.resolve.extensions = [".tsx", ".ts", ".jsx", ".js", ".scss", ".css", ".json"];

      // 作为子应用接入微前端的打包适配,不接入微前端可以不需要
      webpackConfig.output.library = `${name}-[name]`;
      webpackConfig.output.libraryTarget = "umd";
      webpackConfig.output.globalObject = "window";
      // splitChunks打包优化
      webpackConfig.optimization.splitChunks = {
        ...webpackConfig.optimization.splitChunks,
        cacheGroups: {
          commons: {
            chunks: "all",
            // 将两个以上的chunk所共享的模块打包至commons组。
            minChunks: 2,
            name: "commons",
            priority: 80,
          },
        },
      };
      // 开启持久化缓存
      webpackConfig.cache.type = "filesystem";
      // 生产环境打包优化
      if (env !== "development") {
        webpackConfig.plugins = webpackConfig.plugins.concat(
          new FileManagerPlugin({
            events: {
              onEnd: {
                mkdir: [`zip/${name}/dist`, `zip/${name}/template`],
                copy: [
                  {
                    source: source,
                    destination: `${appBuild}/config.js`,
                  },
                  {
                    source: `${path.resolve("build")}`,
                    destination: `zip/${name}/dist`,
                  },
                  {
                    source: path.resolve("template"),
                    destination: `zip/${name}/template`,
                  },
                ],
                archive: [
                  {
                    source: `zip`,
                    destination: path.relative(__dirname, `./${name}-${version}-SNAPSHOT.tar.gz`),
                    format: "tar",
                    options: {
                      gzip: true,
                      gzipOptions: {
                        level: 1,
                      },
                      globOptions: {
                        nomount: true,
                      },
                    },
                  },
                ],
                delete: ["zip"],
              },
            },
            runTasksInSeries: true,
          }),
          new BundleAnalyzerPlugin({
            analyzerMode: "server",
            analyzerHost: "127.0.0.1",
            analyzerPort: 8889,
            openAnalyzer: true, // 构建完打开浏览器
            reportFilename: path.resolve(__dirname, `analyzer/index.html`),
          }),
          new CompressionWebpackPlugin({
            test: /\.(js|ts|jsx|tsx|css|scss)$/, //匹配要压缩的文件
            algorithm: "gzip",
          }),
        );
        webpackConfig.optimization.minimizer = [
          new TerserPlugin({
            parallel: true, //开启并行压缩,可以加快构建速度
          }),
        ];
        // 生产环境关闭source-map
        webpackConfig.devtool = false;
        // 生产环境移除source-map-loader
        removeLoaders(webpackConfig, loaderByName("source-map-loader"));
      } else {
        addBeforeLoaders(webpackConfig, loaderByName("style-loader"), "thread-loader");
        addBeforeLoaders(webpackConfig, loaderByName("style-loader"), "cache-loader");
      }
      return webpackConfig;
    },
    plugins: [new WebpackBar(), new DllReferencePlugin({ manifest })],
  }),
};



http://www.kler.cn/a/466784.html

相关文章:

  • 基于氢氧燃料电池的分布式三相电力系统Simulink建模与仿真
  • 深入理解 MySQL 的性能调优策略
  • 五类推理(逻辑推理、概率推理、图推理、基于深度学习的推理)的开源库 (一)
  • Linux驱动开发(18):linux驱动并发与竞态
  • 《深入浅出HTTPS​​​​​​​​​​​​​​​​​》读书笔记(24):椭圆曲线密码学
  • 【C++】B2092 开关灯
  • 数仓建模(二) 从关系型数据库到数据仓库的演变
  • 淘宝商品详情API返回值说明:Python爬虫代码示例
  • perf:对hutool的BeanUtil工具类做补充
  • 【51单片机零基础-chapter3:按键:独立按键|||附带常见C语句.逻辑运算符】
  • 中国科技产业化促进会深入深圳企业调研
  • gesp(C++一级)(17)洛谷:B4062:[GESP202412 一级] 温度转换
  • 在Linux系统中使用字符图案和VNC运行Qt Widgets程序
  • IDEA Plugins中搜索不到插件解决办法
  • 自动化测试常考的面试题+答案汇总(持续更新)
  • React 网络请求优化
  • CVSS漏洞评分系统曝出严重缺陷
  • 【源码+文档+调试讲解】“健康早知道”微信小程序
  • 生成对抗网络 (Generative Adversarial Network, GAN) 算法MNIST图像生成任务及CelebA图像超分辨率任务
  • 深入理解 Android 中的 ComponentInfo
  • Hive集群安装部署
  • Markdown中流程图的用法
  • 解决 HTML 表单输入框与按钮对齐问题
  • LeetCode 力扣 热题 100道(二十三)找到字符串中所有字母异位词(C++)
  • issue问题全解
  • 从摩托罗拉手机打印短信的简单方法