React optimization

1. Front-end general optimization. This kind of optimization exists in all front-end frameworks, and the focus is on how to apply these techniques to React components.

2. Reduce unnecessary component updates. This type of optimization is achieved by reducing unnecessary component updates after the component state changes. Corresponding to React, it is: reduce the number of rendered nodes, reduce the complexity of component rendering, and make full use of the cache to avoid re-rendering (using the cache can be considered Use PureComponent, React.memo, hook function useCallback, useMemo, etc.)

PureComponent is a shallow comparison of Props and State of class components; React.memo is a shallow comparison of Props of function components
3. Submit phase optimization. The purpose of this type of optimization is to reduce the commit phase time.
Four, craco optimization

1. Front-end general optimization, also applicable to other frameworks such as vue
1. Component loading on demand, component loading optimization on demand can be divided into three categories: lazy loading, lazy rendering, and virtual list.
virtual list react-window

2. Batch update: a merge objects b use unstable_batchedUpdates
function NormalComponent() { const [list, setList] = useState(null) const [info, setInfo] = useState(null)

useEffect(() => {
;(async () => {
const data = await getApiData()
setList(data.list)
setInfo(data.info)
})()
}, [])

return

Render times when not updating components in batches: {renderOnce("normal")}

}
If you write like this, then this component will trigger the Render process of the component after setList(data.list), and then trigger the Render process again after setInfo(http://data.info), causing performance loss. So how should you write to achieve batch updates?
2.1. Combine multiple states into a single state. For example, use the following code to replace the two States of list and info.
const [data, setData] = useState({ list: null, info: null })

2.2. Use the unstable_batchedUpdates method officially provided by React to encapsulate multiple setStates into the unstable_batchedUpdates callback. The modified code is as follows.
function BatchedComponent() { const [list, setList] = useState(null) const [info, setInfo] = useState(null)

useEffect(() => {
;(async () => {
const data = await getData()
unstable_batchedUpdates(() => {
setList(data.list)
setInfo(data.info)
})
})()
}, [])

return

Render times when updating components in batches: {renderOnce("batched")}

}
3. Update according to priority and respond to users in a timely manner
4. Use debounce and throttle to avoid repeated callbacks
5. Cache optimization
Commonly used useMemo caches the result of the last calculation
. useMemo can only cache the result of the latest function execution. If you want to cache more functions The execution result can use memoizee.

6. The large component is split into several small components, just separate the changed part from the unchanged part. The
change of the three properties of react will cause the component to be re-rendered 1. props 2. state 3. context
1. Do not use any API , just separate the variable part from the constant part.

2. Skip unnecessary component updates
1. PureComponent, React.memo
React.PureComponent is only applicable to class components,

Functional Component, you can use React.memo to achieve the same effect
Combine Immutable.js with PureComponent to avoid unnecessary re-render

Properly use useRef()
When the content of the ref object changes, useRef will not notify the change. Changing the .current property does not cause the component to re-render.

2. shouldComponentUpdate
3. useMemo and useCallback to achieve a stable Props value
4. Publishers and subscribers skip the Render
process of intermediate
components Change the Render process triggered by the callback function 9. Hooks are updated on demand 10. The animation library directly modifies DOM properties



3. Optimization of submission phase
1. Avoid updating component State in didMount and didUpdate

4. Craco packaging optimization
1. Lazy loading of images, css, js, routing components, antd components on demand
2. Third-party packages, use CDN, do not package them in
3. Split js
4. Avoid repeated packaging dependencies
5. Enable GZIP compression

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 })],
  }),
};


V. Summary: We first mastered the workflow of React. A status update of React components is divided into a reconciliation phase and a submission phase. The performance optimization points of the React project are mainly concentrated in the reconciliation stage, and the purpose of optimization is achieved by avoiding unnecessary components from entering the Render process as much as possible. In a real project, if you encounter performance problems, first locate the cause of time-consuming through React Profiler, and then choose an optimization method according to the specific situation.
Select optimization techniques
If there are unnecessary updated components that enter the Render process, choose to skip unnecessary component updates for optimization.
If it is because the page mounts too many invisible components, choose lazy loading, lazy rendering or virtual list for optimization.
If multiple status updates are caused by setting the status multiple times, select batch updates or callbacks that are frequently triggered by debounce and throttle optimization for optimization.
If the Render logic of the component is really time-consuming, we need to locate the time-consuming code first and determine whether it can be optimized through caching. If so, choose cache optimization, otherwise choose to update by priority, respond to users in a timely manner, and disassemble component logic to respond to users faster

Reference:
https://zhuanlan.zhihu.com/p/425635864?utm_id=0

Guess you like

Origin blog.csdn.net/hkduan/article/details/123918410