React 性能优化最佳实践
React 性能优化是提升用户体验的关键。本文将全面介绍 React 性能优化的最佳实践。
一、性能分析
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> ); }
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> ); }
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> ); }
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> ); }
function Parent() { const [count, setCount] = useState(0);
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
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>; }
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> ); }
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
{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> ); }
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);
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>; }
const MemoizedChild = React.memo(Child);
|
五、渲染优化
5.1 使用 React.lazy 和 Suspense
import React, { lazy, Suspense, useState } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
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> ); }
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> ); }
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
function useComponentLifecycle() { useEffect(() => { console.log('组件已挂载'); return () => { console.log('组件已卸载'); }; }, []);
return null; }
function Component() { useComponentLifecycle(); return <div>内容</div>; }
|
七、总结
7.1 优化技巧
- React.memo:避免不必要的重新渲染
- useMemo:缓存计算结果
- useCallback:缓存函数
- 代码分割:减少初始包大小
- 虚拟列表:处理大量数据
7.2 优化原则
- 测量性能:使用 Profiler 分析
- 优先优化最耗时的操作
- 避免过早优化
- 使用合适的技术
- 持续优化和改进
优化性能,提升用户体验!