10个React性能优化奇淫技巧你需要掌握😏😏😏

哈喽大家好,我是Lotzinfly,一位前端小猎人。欢迎大家来到前端丛林,在这里你将会遇到各种各样的前端猎物,我希望可以把这些前端猎物统统拿下,嚼碎了服用,并成为自己身上的骨肉。

当我们学习前端进阶的时候,React性能优化是重中之重。学会React性能优化将大大提高我们的开发效率,所以一定要掌握好常见的React性能优化场景。 这篇文章让我们分析10个React性能优化技巧,最近秋招就要来了,学会这10个例子就可以大胆和面试官畅所欲言和自信谈薪啦!😏😏😏 这10个React性能优化掰扯并嚼碎了服用将会给我们带来大量经验,你们准备好了吗?话不多说,现在开启我们今天的前端丛林冒险之旅吧!

文章开头

本文是一篇关于React性能优化的技术分享文章,主要面向前端开发人员。文章列出了十大React性能优化技巧,每个技巧都简要说明了其作用和重要性,旨在帮助开发者提升React应用的性能和用户体验。这些技巧包括使用memo、useMemo、useCallback等Hooks来优化组件渲染,利用useTransition和useDeferredValue处理非紧急更新,以及通过Fragment减少DOM节点等。

React性能优化十大技巧

前端开发必看!React性能优化是提升用户体验和应用程序响应速度的关键。今天,我们将深入探讨十大超实用的React性能优化技巧,并通过具体例子加以说明。

1. memo - 组件props不变时跳过重渲染

memo是React提供的一个高阶组件,它可以帮助我们避免不必要的组件重渲染。当组件的props没有发生变化时,memo会跳过该组件的重渲染,从而节省性能。

例子

jsx 复制代码
import React, { memo } from 'react';
 
const MyComponent = memo(({ value }) => {
  console.log('Component rendered');
  return <div>{value}</div>;
});
 
function App() {
  const [count, setCount] = React.useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <MyComponent value="This won't re-render on count change" />
    </div>
  );
}

在这个例子中,无论count如何变化,MyComponent都不会重新渲染,因为它的props没有改变。

2. useMemo - 缓存计算结果

useMemo是一个React Hook,用于缓存计算结果,避免在每次渲染时都进行昂贵的计算。

例子

jsx 复制代码
import React, { useMemo, useState } from 'react';
 
function ExpensiveCalculation({ a, b }) {
  const result = useMemo(() => {
    console.log('Performing expensive calculation');
    // 模拟一个昂贵的计算过程
    let sum = 0;
    for (let i = 0; i <= a * b; i++) {
      sum += i;
    }
    return sum;
  }, [a, b]); // 只有当a或b变化时,才会重新计算
 
  return <div>Result: {result}</div>;
}
 
function App() {
  const [a, setA] = useState(10);
  const [b, setB] = useState(20);
 
  return (
    <div>
      <button onClick={() => setA(a + 1)}>Increment A</button>
      <button onClick={() => setB(b + 1)}>Increment B</button>
      <ExpensiveCalculation a={a} b={b} />
    </div>
  );
}

在这个例子中,ExpensiveCalculation组件只有在ab变化时才会重新计算结果。

3. useCallback - 缓存函数引用

useCallback用于缓存函数的引用,避免在每次渲染时都创建新的函数实例,从而减少子组件的不必要重渲染。

例子

jsx 复制代码
import React, { useCallback, useState } from 'react';
 
function ChildComponent({ onClick }) {
  console.log('Child component rendered');
  return <button onClick={onClick}>Click me</button>;
}
 
function ParentComponent() {
  const [count, setCount] = useState(0);
 
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]); // 只有当count变化时,才会创建新的函数实例
 
  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <div>Count: {count}</div>
    </div>
  );
}

在这个例子中,handleClick函数只有在count变化时才会重新创建,从而避免了ChildComponent的不必要重渲染。

4. useTransition - 优先处理重要任务

useTransition是React 18引入的一个新Hook,用于将不紧急的更新标记为过渡更新,从而允许浏览器优先处理重要的交互任务。

例子

jsx 复制代码
import React, { useState, useTransition } from 'react';
 
function App() {
  const [items, setItems] = useState([]);
  const [isPending, startTransition] = useTransition();
 
  const handleClick = () => {
    startTransition(() => {
      // 模拟一个耗时的数据加载过程
      const newItems = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);
      setItems(newItems);
    });
  };
 
  return (
    <div>
      <button onClick={handleClick}>Load Items</button>
      {isPending && <div>Loading...</div>}
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

在这个例子中,点击按钮后,数据加载过程被标记为过渡更新,从而允许浏览器在加载数据的同时保持响应。

5. useDeferredValue - 延迟渲染不紧急内容

useDeferredValue允许你延迟渲染一些不紧急的内容,类似于防抖但更智能,能够自动调节延迟时间。

例子

jsx 复制代码
import React, { useState, useDeferredValue } from 'react';
 
function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
 
  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <div>
        {deferredText.split('').map((char, index) => (
          <span key={index}>{char}</span>
        ))}
      </div>
    </div>
  );
}

在这个例子中,输入框的实时变化不会立即导致下方字符的重新渲染,而是会被useDeferredValue延迟处理,从而提升性能。

6. Fragment - 减少DOM节点

Fragment允许你将多个子元素包裹在一个组中,而不需要添加额外的DOM节点。

例子

jsx 复制代码
import React, { Fragment } from 'react';
 
function List({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <Fragment key={item.id}>
          <li>{item.name}</li>
          {item.description && <li>{item.description}</li>}
        </Fragment>
      ))}
    </ul>
  );
}

在这个例子中,使用Fragment避免了为每个列表项添加额外的容器元素。

7. 合理使用Context

Context提供了一种在组件树中传递数据的方法,但是它的更新会强制所有使用该Context的组件重渲染。因此,要谨慎使用Context,并考虑将其拆分为多个小的Context以减少不必要的重渲染。

例子

jsx 复制代码
import React, { createContext, useContext, useState } from 'react';
 
const ThemeContext = createContext();
 
function ThemeButton() {
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme.background, color: theme.foreground }}>Click me</button>;
}
 
function App() {
  const [theme, setTheme] = useState({ background: 'black', foreground: 'white' });
 
  return (
    <ThemeContext.Provider value={theme}>
      <ThemeButton />
    </ThemeContext.Provider>
  );
}

在这个例子中,ThemeContext被用来传递主题数据,但是要注意,如果theme对象频繁变化,可能会导致所有使用ThemeContext的组件重渲染。

8. 避免index作为key

在使用列表渲染时,避免使用index作为key,因为这可能导致列表操作时复用率低,甚至引发bug。应该使用数据中的唯一标识符作为key

例子

jsx 复制代码
import React from 'react';
 
function List({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li> // 使用item.id作为key
      ))}
    </ul>
  );
}

在这个例子中,使用item.id作为key可以确保在列表操作时正确复用组件。

9. 懒加载 - 按需加载模块

使用React.lazySuspense可以实现代码分割和懒加载,从而加快首屏加载速度。

例子

js 复制代码
import React, { lazy, Suspense } from 'react';
 
const LazyComponent = lazy(() => import('./LazyComponent'));
 
function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

在这个例子中,LazyComponent只有在需要时才会被加载和渲染。

10. 组件卸载时的清理

在组件卸载时,一定要记得清理定时器、监听器等资源,否则可能会导致内存泄漏和应用不稳定。可以使用useEffectreturn函数来进行清理。

例子

js 复制代码
import React, { useEffect, useState } from 'react';
 
function TimerComponent() {
  const [count, setCount] = useState(0);
 
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1);
    }, 1000);
 
    // 清理函数
    return () => {
      clearInterval(timer);
    };
  }, [count]); // 注意:这里的依赖项可能导致一些问题,实际使用中可能需要调整
 
  // 更合理的写法可能是将timer的设定和清理完全放在useEffect中,不依赖count
  // 下面是一个修正后的例子
  const betterUseEffectExample = () => {
    useEffect(() => {
      const timerId = setInterval(() => {
        // 这里不直接更新state,或者使用其他状态管理方式
        console.log('Timer tick');
      }, 1000);
      return () => clearInterval(timerId);
    }, []); // 空依赖数组表示只在组件挂载和卸载时执行
  };
 
  return <div>Count: {count}</div>;
}
 
// 在实际应用中,应该像下面这样使用
function CorrectTimerComponent() {
  const [count, setCount] = useState(0);
 
  useEffect(() => {
    const updateCount = () => {
      setCount(c => c + 1); // 使用函数式更新避免依赖
    };
    const timerId = setInterval(updateCount, 1000);
    return () => clearInterval(timerId);
  }, []); // 空依赖数组
 
  return <div>Count: {count}</div>;
}

在上面的修正例子中,CorrectTimerComponent展示了如何在组件卸载时正确清理定时器,同时避免了在useEffect的依赖数组中不必要地添加count,而是使用了函数式更新setCount(c => c + 1)

希望这些扩展的例子能够帮助你更好地理解和应用这些React性能优化技巧!

相关推荐
JarvanMo3 小时前
我尝试了Appwrite, Supabase和 Firebase Databases
前端·后端
Hilaku3 小时前
前端的单元测试,大部分都是在自欺欺人
前端·javascript·单元测试
一枚前端小能手3 小时前
🔥 字符串处理又踩坑了?JavaScript字符串方法全攻略,这些技巧让你效率翻倍
前端·javascript
windliang3 小时前
一文入门 agent:从理论到代码实战
前端·算法
4z333 小时前
Jetpack Compose重组原理(一):快照系统如何精准追踪状态变化
前端·android jetpack
三十_3 小时前
私有 npm 仓库实践:Verdaccio 保姆级搭建教程与最佳实践
前端·npm
uhakadotcom3 小时前
分享近期学到的postgresql的几个实用的新特性!
后端·面试·github
叫我詹躲躲3 小时前
别再手写正则了!20 + 证件 / 手机号 / 邮箱验证函数,直接复制能用
前端·javascript·正则表达式
猪哥帅过吴彦祖3 小时前
第 4 篇:赋予表面生命 - WebGL 纹理贴图
前端·javascript·webgl