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

【react】常见的性能优化 1

目录

常见的 React 性能优化手段

1. 使用 useMemo 和 useCallback 缓存数据和函数

2. 使用 React.memo 缓存组件

3. 组件懒加载

4. 合理使用 key

5. 在组件销毁时清除定时器/事件

6. 使用 Suspense 和 Lazy 拆分组件

7. 使用 Fragment 避免额外标记

8. 避免使用内联函数

9. 避免使用内联样式

10. 优化渲染条件

11. 为组件创建错误边界

12. 组件卸载前进行清理操作

13. 使用 PureComponent

14. 使用 shouldComponentUpdate

15. 在构造函数中进行函数 this 绑定

16. 类组件中的箭头函数

17. 避免数据结构突变

18. 依赖优化

常见的 React 性能优化手段

在开发 React 应用时,性能优化是确保应用高效运行的关键。以下是常见的 React 性能优化手段,并附带代码示例和解释。

1. 使用 useMemo 和 useCallback 缓存数据和函数

useMemo 和 useCallback 是 React 提供的 Hooks,用于缓存计算结果和函数引用,避免不必要的重新渲染。

示例:

import React, { useState, useMemo, useCallback } from 'react';

const data = {
  userName: '张三',
  age: 19,
  fav: '篮球、排球',
};

const getUserInfo = () => {
  return {
    ...data,
    random: Math.random(),
  };
};

function Case2() {
  const [count, setCount] = useState(0);

  // 使用 useMemo 缓存数据
  const userInfo = useMemo(() => getUserInfo(), []);

  // 使用 useCallback 缓存函数
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <div>姓名:{userInfo.userName}</div>
      <div>年龄:{userInfo.age}</div>
      <div>爱好:{userInfo.fav}</div>
      <div>随机数: {userInfo.random}</div>
      <div>当前页面渲染次数: {count}</div>
      <button onClick={handleClick}>刷新渲染组件</button>
    </div>
  );
}

解释:useMemo 确保 getUserInfo 的结果只在首次渲染时计算一次。

   useCallback 确保 handleClick 函数的引用不会在每次渲染时都重新创建。

2. 使用 React.memo 缓存组件

React.memo 类似于 shouldComponentUpdate,当 props 没有变化时,不会重新渲染组件,从而提高性能。

示例:

import React from 'react';
import { Button } from 'antd';

const BasicButton = (props) => {
  return <Button {...props}></Button>;
};

export default React.memo(BasicButton, (oldProps, newProps) => {
  return oldProps === newProps; // true - 不更新 false - 更新
});

解释:React.memo 接收一个比较函数,只有当 props 发生变化时才会重新渲染组件。

3. 组件懒加载

使用组件懒加载可以减少初始 bundle 文件大小,加快组件加载速度。

示例:

import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import(/* webpackChunkName: "Home" */ './Home'));
const List = lazy(() => import(/* webpackChunkName: "List" */ './List'));

function App() {
  return (
    <BrowserRouter>
      <Link to="/">Home</Link>
      <Link to="/list">List</Link>
      <Switch>
        <Suspense fallback={<div>Loading</div>}>
          <Route path="/" component={Home} exact />
          <Route path="/list" component={List} />
        </Suspense>
      </Switch>
    </BrowserRouter>
  );
}

export default App;

解释:lazy 动态导入组件,Suspense 提供加载状态。

4. 合理使用 key

在 map 循环中尽量使用唯一的标识作为 key,避免使用 index 作为 key,方便复用组件。

示例:

import React from 'react';

const list = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 3, name: '王五' },
];

function Case3() {
  return (
    <div>
      {list.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

export default Case3;

解释:使用 id 作为 key,确保每个元素的唯一性。

5. 在组件销毁时清除定时器/事件

组件卸载时清除相关事件、定时器,防止内存泄漏。

示例:

import React, { useEffect, useState } from 'react';

function Case1() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1);
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, [count]);

  return <div>{count}</div>;
}

export default Case1;

解释:useEffect 返回的清理函数会在组件卸载时执行,清除定时器。

6. 使用 Suspense 和 Lazy 拆分组件

通过 Suspense 和 Lazy 实现按需加载组件,提升首屏加载速度。

示例:

import React, { lazy } from 'react';
import ReactDOM from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const LearnReactOptimize = lazy(() => import('./pages/LearnReactOptimize'));

const LazyBoundary = (WrapComp) => (
  <Suspense fallback={<div>loading....</div>}>
    <WrapComp />
  </Suspense>
);

const routeConfig = createBrowserRouter([
  {
    path: '/LearnReactOptimize',
    element: LazyBoundary(LearnReactOptimize),
  },
  {
    path: '/',
    element: <App />,
  },
]);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RouterProvider router={routeConfig} />
  </React.StrictMode>,
);

解释:Suspense 提供加载状态,Lazy 动态导入组件。

7. 使用 Fragment 避免额外标记

通过 Fragment 减少不必要的标签,简化 DOM 结构。

示例:

import React from 'react';

// bad
function AppBad() {
  return (
    <div>
      <div>1</div>
      <div>2</div>
    </div>
  );
}

// good
function AppGood() {
  return (
    <>
      <div>1</div>
      <div>2</div>
    </>
  );
}

解释:Fragment (<>...</>) 不会生成额外的 DOM 元素。

8. 避免使用内联函数

避免在 JSX 中使用内联函数,以减少不必要的函数创建。

示例:

import React from 'react';

// bad
function AppBad() {
  const handleClick = () => {
    console.log('click');
  };

  return <div onClick={() => handleClick()}>App</div>;
}

// good
function AppGood() {
  const handleClick = () => {
    console.log('click');
  };

  return <div onClick={handleClick}>App</div>;
}

解释:内联函数每次渲染都会创建新的函数实例,导致不必要的性能开销。

9. 避免使用内联样式

避免使用内联样式,将样式提取到 CSS 文件中,减少 JavaScript 执行时间。

示例:

import React from 'react';
import './App.css'; // 引入外部 CSS 文件

function App() {
  return <div className="app-style">App works</div>;
}

export default App;

解释:将样式提取到外部 CSS 文件中,减少 JavaScript 执行时间。

10. 优化渲染条件

避免不必要的渲染,确保组件只在必要时更新。

示例:

import React, { useState, useEffect } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [name] = useState("张三");

  useEffect(() => {
    setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
  }, []);

  return (
    <div>
      {count}
      <ShowNameMemo name={name} />
    </div>
  );
}

function ShowName({ name }) {
  console.log("showName render...");
  return <div>{name}</div>;
}

const ShowNameMemo = React.memo(ShowName);

export default App;

解释:使用 React.memo 防止不必要的重新渲染。

11. 为组件创建错误边界

捕获子组件中的错误,防止整个组件树崩溃。

示例:

import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

解释:ErrorBoundary 捕获子组件中的错误,显示降级 UI。

12. 组件卸载前进行清理操作

确保组件卸载时清理定时器、事件监听等资源。

示例:

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    let timer = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  return (
    <button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>
      {count}
    </button>
  );
};

export default App;

解释:useEffect 返回的清理函数会在组件卸载时执行,清除定时器。

13. 使用 PureComponent

PureComponent 是类组件的优化版本,自动实现浅比较,减少不必要的渲染。

示例:

import React from 'react';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 1,
    };
  }

  componentDidMount() {
    this.setState({ count: 1 });
  }

  render() {
    return (
      <div>
        <RegularChildComponent count={this.state.count} />
        <PureChildComponent count={this.state.count} />
      </div>
    );
  }
}

class RegularChildComponent extends React.Component {
  render() {
    console.log('RegularChildComponent render');
    return <div>{this.props.count}</div>;
  }
}

class PureChildComponent extends React.PureComponent {
  render() {
    console.log('PureChildComponent render');
    return <div>{this.props.count}</div>;
  }
}

export default App;

解释:PureComponent 自动实现浅比较,减少不必要的渲染。

14. 使用 shouldComponentUpdate

手动控制组件是否需要更新。

示例:

import React from 'react';

export default class App extends React.Component {
  constructor() {
    super();
    this.state = { name: '张三', age: 20, job: 'waiter' };
  }

  componentDidMount() {
    setTimeout(() => this.setState({ job: 'chef' }), 1000);
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.name !== nextState.name || this.state.age !== nextState.age) {
      return true;
    }
    return false;
  }

  render() {
    console.log('rendering');
    let { name, age } = this.state;
    return <div>{name} {age}</div>;
  }
}

解释:shouldComponentUpdate 控制组件是否需要更新。

15. 在构造函数中进行函数 this 绑定

确保类方法中的 this 指向正确。

示例:

import React from 'react';

export default class App extends React.Component {
  constructor() {
    super();
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(this);
  }

  render() {
    return <button onClick={this.handleClick}>按钮</button>;
  }
}

解释:构造函数中绑定 this,确保方法中的 this 指向正确。

16. 类组件中的箭头函数

使用箭头函数避免 this 绑定问题。

示例:

import React from 'react';

export default class App extends React.Component {
  handleClick = () => console.log(this);

  render() {
    return <button onClick={this.handleClick}>按钮</button>;
  }
}

解释:箭头函数自动绑定 this,避免手动绑定。

17. 避免数据结构突变

保持组件中 props 和 state 的数据结构一致,避免突变。

示例:

import React, { Component } from 'react';

export default class App extends Component {
  constructor() {
    super();
    this.state = {
      employee: {
        name: '张三',
        age: 20,
      },
    };
  }

  render() {
    const { name, age } = this.state.employee;
    return (
      <div>
        {name}
        {age}
        <button
          onClick={() =>
            this.setState({
              ...this.state,
              employee: {
                ...this.state.employee,
                age: 30,
              },
            })
          }
        >
          change age
        </button>
      </div>
    );
  }
}

解释:使用扩展运算符避免直接修改对象。

18. 依赖优化

优化第三方库的引入,减少打包体积。

示例:

  1. 安装依赖:

    yarn add react-app-rewired customize-cra lodash babel-plugin-lodash
  2. 创建 config-overrides.js

    const { override, useBabelRc } = require('customize-cra');
    
    module.exports = override(useBabelRc());
  3. 修改 package.json

    "scripts": {
      "start": "react-app-rewired start",
      "build": "react-app-rewired build",
      "test": "react-app-rewired test --env=jsdom",
      "eject": "react-scripts eject"
    }
  4. 创建 .babelrc

    {
      "plugins": ["lodash"]
    }
  5. 使用 Lodash:

    import React from 'react';
    import _ from 'lodash';
    
    function App() {
      console.log(_.chunk(['a', 'b', 'c', 'd'], 2));
      return <div>App works</div>;
    }
    
    export default App;

解释:使用 react-app-rewired 和 customize-cra 覆盖默认配置,babel-plugin-lodash 优化 Lodash 的引入。

码字不易,大佬们点点赞


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

相关文章:

  • 如何用Python爬虫获得淘宝商品详情API返回值说明
  • 在 HTC 手机和计算机之间快速传输视频/电影
  • asp.net core webapi中的数据注解与数据验证
  • Flink使用
  • Three.js教程011:贴图的加载与环境遮蔽贴图强度设置
  • 计算机网络--应用层--HTTP
  • 【HDU】1090 A+B for Input-Output Practice (II)
  • maven的中国镜像有哪些
  • 【QED】魔咒解密
  • USB子系统学习(一)USB电气信号
  • Nginx——静态资源部署(二/五)
  • 基于word2vec的文本大数据分析
  • OpenCV计算机视觉 05 图像边缘检测(Sobel算子、Scharr算子、Laplacian算子、Canny边缘检测)
  • 印象笔记07——试一试PDF标注
  • Go语言的 的引用数据类型(Reference Data Types)基础知识
  • 【DevOps】Jenkins项目发布
  • Linux驱动开发(17):输入子系统–电阻触摸驱动实验
  • 宝塔docker安装milvus向量库
  • React快速上手到项目实战总篇
  • [网络安全]DVWA之Brute Force攻击姿势及解题详析合集