React性能优化分享

作者 : admin 本文共4380个字,预计阅读时间需要11分钟 发布时间: 2024-06-16 共1人阅读

本篇将介绍在React编码过程中需要注意的性能优化点。鉴于图片懒加载、虚拟滚动列表等已成为广为人知的通用性能优化手段,本文将不再赘述这些内容。

  1. memo

    memo允许组件在 props 没有改变的情况下跳过重新渲染

    默认通过Object.is比较每个prop,可通过第二个参数,传入自定义函数来控制对比过程

    const Chart = memo(function Chart({ dataPoints }) {
      // ...
    }, arePropsEqual);
    
    function arePropsEqual(oldProps, newProps) {
      return (
        oldProps.dataPoints.length === newProps.dataPoints.length &&
        oldProps.dataPoints.every((oldPoint, index) => {
          const newPoint = newProps.dataPoints[index];
          return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;
        })
      );
    }
    
  2. useMemo

    在每次重新渲染的时候能够缓存计算的结果,只有传入的参数发生变化后,该计算函数才会重新调用计算新的结果。

    import { useState, useMemo } from "react";
    
    function App() {
      const [count, setCount] = useState(0);
    
      const memoizedValue = useMemo(() => {
        //创建1000位数组
        const list = new Array(1000).fill(null).map((_, i) => i);
    
        //对数组求和
        const total = list.reduce((res, cur) => (res += cur), 0);
    
        //返回计算的结果
        return count + total;
    
        //添加依赖项,只有count改变时,才会重新计算
      }, [count]);
    
      return (
        
          {memoizedValue}
          
        
      );
    }
    
    export default App;
    
  3. useCallback

    缓存函数的引用地址,仅在依赖项改变时才会更新。避免在每次渲染时都创建新的函数,useCallback 返回一个记忆化的回调函数,它只在其依赖项改变时才会改变

    import { useState, memo } from 'react';
    
    const App = () => {
      const [count, setCount] = useState(0);
    
      const handleClick = () => {
        setCount((prev) => prev + 1);
      };
    
      return (
        
          {count}
          
        
      );
    };
    
    const MyButton = memo(function MyButton({ handleClick }: { handleClick: () => void }) {
      console.log('子组件渲染');
      return ;
    });
    
    export default App;
    

    点击按钮,可以发现即使子组件使用memo包裹了,但还是更新了,控制台打印出“子组件渲染”。这是因为父组件App每次更新时,函数handleClick每次都返回了新的引用地址,因此对于子组件来说每次传入的都是不一样的值,从而触发重渲染。

    同样的,减少使用通过内联函数绑定事件。每次父组件更新时,匿名函数都会返回一个新的引用地址,从而触发子组件的重渲染

     setCount((prev) => prev + 1)} />
    

    使用useCallback可以缓存函数的引用地址,将handleClick改为

    const handleClick = useCallback(()=>{  setCount(prev=>prev+1)},[])
    

    再点击按钮,会发现子组件不会再重新渲染。

  4. useTransition

    使用useTransition提供的startTransition来标记一个更新作为不紧急的更新。这段任务可以接受延迟或被打断渲染,进而去优先考虑更重要的任务执行

    页面会先显示list2的内容,之后再显示list1的内容

    import { useState, useEffect, useTransition } from "react";
    
    const App = () => {
      const [list1, setList1] = useState([]);
      const [list2, setList2] = useState([]);
      const [isPending, startTransition] = useTransition();
      useEffect(() => {
        startTransition(() => {
           //将状态更新标记为 transition  
          setList1(new Array(10000).fill(null));
        });
      }, []);
      useEffect(()=>{
        setList2(new Array(10000).fill(null));
      },[])
      return (
        
          {isPending ? "pending" : "nopending"}
          {list1.map((_, i) => (
            {i}
          ))}
          -----------------list2
          {list2.map((_, i) => (
            6666
          ))}
        
      );
    };
    
    export default App;
    
  5. useDeferredValue

    可以让我们延迟渲染不紧急的部分,类似于防抖但没有固定的延迟时间

    import { useState, useDeferredValue } from 'react';
    
    function SearchPage() {
      const [query, setQuery] = useState('');
      const deferredQuery = useDeferredValue(query);
      // ...
    }
    
  6. Fragment

    当呈现多个元素而不需要额外的容器元素时,使用React.Fragment可以减少DOM节点的数量,从而提高呈现性能

    const MyComponent = () => {
      return (
        
          Element 1
          Element 2
          Element 3
        
      );
    };
    
  7. 合理使用Context

    Context 能够在组件树间跨层级数据传递,正因其这一独特机制,Context 可以绕过 React.memo 或 shouldComponentUpdate 设定的比较过程。

    也就是说,一旦 Context 的 Value 变动,所有使用 useContext 获取该 Context 的组件会全部 forceUpdate。即使该组件使用了memo,且 Context 更新的部分 Value 与其无关

    为了使组件仅在 context 与其相关的value发生更改时重新渲染,将组件分为两个部分。在外层组件中从 context 中读取所需内容,并将其作为 props 传递给使用memo优化的子组件。

  8. 尽量避免使用index作为key

    在渲染元素列表时,尽量避免将数组索引作为组件的key。如果列表项有添加、删除及重新排序的操作,使用index作为key,可能会使节点复用率变低,进而影响性能

    使用数据源的id作为key

    const MyComponent = () => {
      const items = [{ id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }, { id: 3, name: "Item 3" }];
    
      return (
          
      {items.map(item => (
    • {item.name}
    • ))}
    ); };
  9. 懒加载

    通过React.lazy和React.Suspense实施代码分割策略,将React应用细分为更小的模块,确保在具体需求出现时才按需加载相应的部分

    定义路由

    import { lazy } from 'react';
    import { createBrowserRouter } from 'react-router-dom';
    
    const Login = lazy(() => import('../pages/login'));
    
    const routes = [
      {
        path: '/login',
        element: ,
      },
    ];
    
    //可传第二个参数,配置base路径 { basename: "/app"}
    const router = createBrowserRouter(routes);
    
    export default router;
    

    引用路由

    import { Suspense } from 'react';
    import { RouterProvider } from 'react-router-dom';
    
    import ReactDOM from 'react-dom/client';
    
    import router from './router';
    
    const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
    
    root.render(
      
        
      ,
    );
    
  10. 组件卸载时的清理

    在组件卸载时清理全局监听器、定时器等。防止内存泄漏影响性能

    import { useState, useEffect, useRef } from 'react';
    
    function MyComponent() {
      const [count, setCount] = useState(0);
      const timer = useRef();
    
      useEffect(() => {
        // 定义定时器
        timer.current = setInterval(() => {
          setCount((count) => count + 1);
        }, 1000);
    
        const handleOnResize = () => {
          console.log('Window resized');
        };
    
        // 定义监听器
        window.addEventListener('resize', handleOnResize);
    
        // 在组件卸载时清除定时器和监听器
        return () => {
          clearInterval(timer.current);
          window.removeEventListener('resize', handleOnResize);
        };
      }, []);
    
      return (
        
          

    {count}

    ); } export default MyComponent;
  11. Hooks

    使用React Hooks可以更方便的管理组件的状态和副作用,减少组件的复杂度和重复代码。

  12. 使用React.PureComponent , shouldComponentUpdate

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

本站无任何商业行为
个人在线分享 » React性能优化分享
E-->