𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

React 性能优化最佳实践

React 性能优化是提升用户体验的关键。本文将全面介绍 React 性能优化的最佳实践。

一、性能分析

1.1 使用 React DevTools Profiler

# 安装 React DevTools
npm install --save-dev @welldone-software/why-did-you-render
// 在组件中使用
import React, { useState } from 'react';
import { whyDidYouRender } from '@welldone-software/why-did-you-render';

// 开启检测
whyDidYouRender(React, {
trackAllPureComponents: true
});

function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}

// 检测不必要的重新渲染

1.2 分析渲染性能

import { Profiler } from 'react';

function App() {
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log(`${id} render time: ${actualDuration}ms`);
}

return (
<Profiler id="App" onRender={onRenderCallback}>
<Counter />
</Profiler>
);
}

二、优化策略

2.1 React.memo

import React, { useState } from 'react';

function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent 渲染');
// 复杂的计算
const expensiveCalculation = data.map(item => item * 2);

return (
<div>
<p>{data.join(', ')}</p>
<p>{expensiveCalculation.join(', ')}</p>
</div>
);
}

// 使用 React.memo 包装
const MemoizedComponent = React.memo(ExpensiveComponent);

function App() {
const [data, setData] = useState([1, 2, 3, 4, 5]);

return (
<div>
<MemoizedComponent data={data} />
<button onClick={() => setData([...data, 6])}>添加数据</button>
</div>
);
}

// 不使用 React.memo 的版本
function WithoutMemo({ data }) {
console.log('WithoutMemo 渲染');
// 每次都会渲染
return <div>{data.join(', ')}</div>;
}

function AppWithoutMemo() {
const [data, setData] = useState([1, 2, 3, 4, 5]);
return (
<div>
<WithoutMemo data={data} />
<button onClick={() => setData([...data, 6])}>添加数据</button>
</div>
);
}

2.2 useMemo

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

function ExpensiveList({ items }) {
// 每次渲染都会重新计算
const sortedItems = items.sort((a, b) => a - b);
console.log('sortedItems 重新计算');

return (
<ul>
{sortedItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
}

// 使用 useMemo 优化
function OptimizedList({ items }) {
const sortedItems = useMemo(() => {
console.log('sortedItems 计算一次');
return items.sort((a, b) => a - b);
}, [items]);

return (
<ul>
{sortedItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
}

function App() {
const [items, setItems] = useState([5, 2, 8, 1, 9]);
const [count, setCount] = useState(0);

return (
<div>
<ExpensiveList items={items} />
<OptimizedList items={items} />
<button onClick={() => setCount(c => c + 1)}>添加</button>
<button onClick={() => setCount(c => c + 1)}>添加</button>
<button onClick={() => setCount(c => c + 1)}>添加</button>
<button onClick={() => setCount(c => c + 1)}>添加</button>
</div>
);
}

2.3 useCallback

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

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

// 每次渲染都创建新函数
const handleClick = () => {
setCount(c => c + 1);
};

return (
<div>
<Child onClick={handleClick} />
<button onClick={handleClick}>添加 ({count})</button>
</div>
);
}

// 使用 useCallback 优化
function Parent() {
const [count, setCount] = useState(0);

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

return (
<div>
<Child onClick={handleClick} />
<button onClick={handleClick}>添加 ({count})</button>
</div>
);
}

function Child({ onClick }) {
console.log('Child 渲染');
return <button onClick={onClick}>点击</button>;
}

2.4 代码分割

import React, { lazy, Suspense } from 'react';

// 动态导入
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
return (
<Suspense fallback={<p>加载中...</p>}>
<HeavyComponent />
</Suspense>
);
}

2.5 避免不必要的 state

// 不好的做法:多个 state
function Counter() {
const [count, setCount] = useState(0);
const [countText, setCountText] = useState('0');

// 每次都重新计算
const updateCountText = (newCount) => {
setCountText(newCount.toString());
};

useEffect(() => {
updateCountText(count);
}, [count]);

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

// 好的做法:合并 state
function Counter() {
const [state, setState] = useState({
count: 0
});

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

2.6 避免内联函数和对象

// 不好的做法:每次渲染都创建新函数和对象
function Counter() {
const [count, setCount] = useState(0);

const handleClick = () => setCount(c => c + 1);

const style = { color: 'blue', fontSize: '16px' };

return (
<button onClick={handleClick} style={style}>
添加 ({count})
</button>
);
}

// 好的做法:使用 useCallback 和 useMemo
function Counter() {
const [count, setCount] = useState(0);

const handleClick = useCallback(() => setCount(c => c + 1), []);

const style = useMemo(() => ({ color: 'blue', fontSize: '16px' }), []);

return (
<button onClick={handleClick} style={style}>
添加 ({count})
</button>
);
}

三、列表优化

3.1 使用 React.memo

function Item({ item, onSelect }) {
console.log(`Item ${item.id} 渲染`);
return (
<div onClick={() => onSelect(item)}>
<h3>{item.name}</h3>
<p>{item.description}</p>
</div>
);
}

const MemoizedItem = React.memo(Item);

function List({ items, onSelect }) {
return (
<div>
{items.map(item => (
<MemoizedItem key={item.id} item={item} onSelect={onSelect} />
))}
</div>
);
}

3.2 使用 key

// 不好的做法:使用 index 作为 key
{items.map((item, index) => (
<Item key={index} item={item} />
))}

// 好的做法:使用唯一标识
{items.map(item => (
<Item key={item.id} item={item} />
))}

3.3 虚拟列表

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

function VirtualList({ items, itemHeight = 50, visibleCount = 10 }) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef(null);

const totalHeight = items.length * itemHeight;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + visibleCount, items.length);
const visibleItems = items.slice(startIndex, endIndex);

const handleScroll = useCallback((e) => {
setScrollTop(e.target.scrollTop);
}, []);

return (
<div
ref={containerRef}
style={{
height: `${totalHeight}px`,
overflowY: 'auto'
}}
onScroll={handleScroll}
>
<div style={{ transform: `translateY(${startIndex * itemHeight}px)` }}>
{visibleItems.map((item) => (
<div key={item.id} style={{ height: `${itemHeight}px` }}>
{item.content}
</div>
))}
</div>
</div>
);
}

四、状态管理优化

4.1 避免过度使用 state

// 不好的做法:状态分散
function Component() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, setCount3] = useState(0);

const increment = (num, setNum) => {
setNum(prev => prev + 1);
};

return (
<div>
<button onClick={() => increment(count1, setCount1)}>1</button>
<button onClick={() => increment(count2, setCount2)}>2</button>
<button onClick={() => increment(count3, setCount3)}>3</button>
</div>
);
}

// 好的做法:合并 state
function Component() {
const [counts, setCounts] = useState({ count1: 0, count2: 0, count3: 0 });

const increment = (key) => {
setCounts(prev => ({
...prev,
[key]: prev[key] + 1
}));
};

return (
<div>
<button onClick={() => increment('count1')}>1</button>
<button onClick={() => increment('count2')}>2</button>
<button onClick={() => increment('count3')}>3</button>
</div>
);
}

4.2 使用 useMemo 缓存计算结果

function ExpensiveComponent({ items }) {
// 每次渲染都重新计算
const filteredItems = items.filter(item => item.active);

// 使用 useMemo 缓存结果
const filteredItems = useMemo(() => {
return items.filter(item => item.active);
}, [items]);

return <ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}

4.3 使用 React.memo 优化父组件

function Parent() {
const [state, setState] = useState(0);

return (
<div>
<Child state={state} />
<button onClick={() => setState(s => s + 1)}>添加</button>
</div>
);
}

function Child({ state }) {
console.log('Child 渲染');
return <div>状态: {state}</div>;
}

// 使用 React.memo 优化
const MemoizedChild = React.memo(Child);

五、渲染优化

5.1 使用 React.lazy 和 Suspense

import React, { lazy, Suspense, useState } from 'react';

// 动态导入
const HeavyComponent = lazy(() => import('./HeavyComponent'));

// 使用 Suspense
function App() {
const [showHeavy, setShowHeavy] = useState(false);

return (
<div>
{showHeavy ? (
<Suspense fallback={<p>加载中...</p>}>
<HeavyComponent />
</Suspense>
) : (
<p>轻量组件</p>
)}
<button onClick={() => setShowHeavy(!showHeavy)}>切换</button>
</div>
);
}

5.2 避免在渲染函数中创建新函数

// 不好的做法
function Counter() {
const [count, setCount] = useState(0);

const handleClick = () => setCount(c => c + 1);

return (
<div>
<button onClick={handleClick}>添加 ({count})</button>
</div>
);
}

// 好的做法:使用 useCallback
function Counter() {
const [count, setCount] = useState(0);

const handleClick = useCallback(() => setCount(c => c + 1), []);

return (
<div>
<button onClick={handleClick}>添加 ({count})</button>
</div>
);
}

5.3 避免在渲染函数中创建新对象

// 不好的做法
function UserProfile({ user }) {
const userData = {
name: user.name,
age: user.age
};

return (
<div>
<p>姓名: {userData.name}</p>
<p>年龄: {userData.age}</p>
</div>
);
}

// 好的做法:使用 useMemo
function UserProfile({ user }) {
const userData = useMemo(() => ({
name: user.name,
age: user.age
}), [user.name, user.age]);

return (
<div>
<p>姓名: {userData.name}</p>
<p>年龄: {userData.age}</p>
</div>
);
}

六、组件拆分

6.1 拆分大型组件

// 大型组件
function Dashboard() {
return (
<div>
<UserList />
<PostList />
<CommentList />
</div>
);
}

// 拆分后
function Dashboard() {
return (
<div>
<UserList />
<PostList />
<CommentList />
</div>
);
}

6.2 使用 HOC 或自定义 Hook

// 自定义 Hook
function useComponentLifecycle() {
useEffect(() => {
console.log('组件已挂载');
return () => {
console.log('组件已卸载');
};
}, []);

return null;
}

function Component() {
useComponentLifecycle();
return <div>内容</div>;
}

七、总结

7.1 优化技巧

  1. React.memo:避免不必要的重新渲染
  2. useMemo:缓存计算结果
  3. useCallback:缓存函数
  4. 代码分割:减少初始包大小
  5. 虚拟列表:处理大量数据

7.2 优化原则

  1. 测量性能:使用 Profiler 分析
  2. 优先优化最耗时的操作
  3. 避免过早优化
  4. 使用合适的技术
  5. 持续优化和改进

优化性能,提升用户体验!