哈喽大家好,我是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
组件只有在a
或b
变化时才会重新计算结果。
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.lazy
和Suspense
可以实现代码分割和懒加载,从而加快首屏加载速度。
例子:
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. 组件卸载时的清理
在组件卸载时,一定要记得清理定时器、监听器等资源,否则可能会导致内存泄漏和应用不稳定。可以使用useEffect
的return
函数来进行清理。
例子:
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性能优化技巧!