react优化

一、前端通用优化。这类优化在所有前端框架中都存在,重点就在于如何将这些技巧应用在 React 组件中。

二、减少不必要的组件更新。这类优化是在组件状态发生变更后,通过减少不必要的组件更新来实现,对应到 React 中就是:减少渲染的节点 、降低组件渲染的复杂度、充分利用缓存避免重新渲染(利用缓存可以考虑使用PureComponent、React.memo、hook函数useCallback、useMemo等方法)

PureComponent 是对类组件的 Props 和 State 进行浅比较;React.memo 是对函数组件的 Props 进行浅比较
三、提交阶段优化。这类优化的目的是减少提交阶段耗时。
四、craco优化

一、前端通用优化,也适用vue等其他框架
1、组件按需加载,组件按需加载优化又可以分为:懒加载、懒渲染、虚拟列表 三类。
虚拟列表react-window

2、批量更新:a合并对象 b用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 次数:{renderOnce(“normal”)}

}
如果你也是这样写的,那么,这个组件会在 setList(data.list) 后触发组件的 Render 过程,然后在 setInfo(http://data.info) 后再次触发 Render 过程,造成性能损失。那么应该怎么写才能实现批量更新呢?
2.1、将多个 State 合并为单个 State。例如使用 如下代码 替代 list 和 info 两个 State。
const [data, setData] = useState({ list: null, info: null })

2.2、使用 React 官方提供的 unstable_batchedUpdates 方法,将多次 setState 封装到 unstable_batchedUpdates 回调中,修改后代码如下。
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 次数:{renderOnce(“batched”)}

}
3、按优先级更新,及时响应用户
4、利用debounce、throttle 避免重复回调
5.缓存优化
常用 useMemo 缓存上次计算的结果
useMemo 只能缓存最近一次函数执行的结果,如果想缓存更多次函数执行的结果,可使用 memoizee。

6.大组件拆分成若干小组件,只是把变的部分和不变的部分分离开
react这3个属性变化,会引起组件从新渲染1.props 2.state 3.context
1.不使用任何API,只是把变的部分和不变的部分分离开。

二、跳过不必要的组件更新
1、PureComponent、React.memo
React.PureComponent只适用于class组件
在这里插入图片描述
利用pureComponent解决title组件重复渲染的问题
在这里插入图片描述

Functional Component,可以使用React.memo以达到同样的效果
结合使用Immutable.js与PureComponent以避免不必要的Re-render
在这里插入图片描述
或者在最后写:export default React.memo(Title)
或者Title = React.memo(Title)

适当用useRef()
当 ref 对象内容发生变化时,useRef 并不会通知变更。变更 .current 属性不会引发组件重新渲染。

2、 shouldComponentUpdate
3、useMemo、useCallback 实现稳定的 Props 值
4、发布者订阅者跳过中间组件 Render 过程
5、状态下放,缩小状态影响范围
6、列表项使用 key 属性
7、useMemo 返回虚拟 DOM
8、跳过回调函数改变触发的 Render 过程
9、 Hooks 按需更新
10、动画库直接修改 DOM 属性

三、提交阶段优化
1、避免在 didMount、didUpdate 中更新组件 State

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

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


五、总结:
React.memo、useMemo、useCallback、useRef都是React进行性能优化的手段,不过我们一定要记得合理运用,不能过度使用,因为深究这几个方法的实现其实都是借助了闭包,会一直占用我们的内存,运用不当可能会导致反向的性能优化问题~
选择优化技巧
如果是因为存在不必要更新的组件进入了 Render 过程,则选择跳过不必要的组件更新进行优化。
如果是因为页面挂载了太多不可见的组件,则选择懒加载、懒渲染或虚拟列表进行优化。
如果是因为多次设置状态,引起了多次状态更新,则选择批量更新或debounce、throttle 优化频繁触发的回调进行优化。
如果组件 Render 逻辑的确非常耗时,我们需要先定位到耗时代码,并判断能否通过缓存优化它。如果能,则选择缓存优化,否则选择按优先级更新,及时响应用户,将组件逻辑进行拆解,以便更快响应用户

参考资料:
https://zhuanlan.zhihu.com/p/425635864?utm_id=0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端段

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值