您现在的位置是:网站首页 > 不优化渲染(每秒 'setState' 100 次)文章详情
不优化渲染(每秒 'setState' 100 次)
陈川
【
前端综合
】
22370人已围观
6162字
理解渲染优化的必要性
React应用中频繁调用setState
会导致性能问题。每次状态更新都会触发重新渲染,如果每秒执行100次setState
,浏览器将不堪重负。这种场景常见于实时数据展示、动画效果或高频用户交互场景中。
class Counter extends React.Component {
state = { count: 0 };
componentDidMount() {
setInterval(() => {
this.setState({ count: this.state.count + 1 });
}, 10); // 每秒100次更新
}
render() {
return <div>{this.state.count}</div>;
}
}
高频更新的性能影响
浏览器主线程被持续占用,导致以下问题:
- 帧率下降,动画卡顿
- 用户输入响应延迟
- 移动设备电池快速耗尽
- 复杂组件树情况下问题加剧
使用Chrome DevTools的Performance面板可以清晰观察到:
- 大量
Update
和Reconciliation
标记 - 长任务阻塞主线程
- 帧时间经常超过16ms(60FPS标准)
原生解决方案对比
对比不同技术方案的性能表现:
方案 | 更新频率 | CPU占用 | 流畅度 |
---|---|---|---|
直接setState | 100次/秒 | 85% | 卡顿 |
requestAnimationFrame | 60次/秒 | 45% | 较流畅 |
Web Workers | 100次/秒 | 30% | 流畅 |
Canvas渲染 | 100次/秒 | 25% | 极流畅 |
优化策略实践
节流与防抖
// 防抖实现
const debounce = (func, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => func(...args), delay);
};
};
// 节流实现
const throttle = (func, limit) => {
let lastFunc;
let lastRan;
return (...args) => {
if (!lastRan) {
func(...args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func(...args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
};
使用React.memo
const ExpensiveComponent = React.memo(({ data }) => {
// 复杂渲染逻辑
return <div>{data}</div>;
});
function Parent() {
const [value, setValue] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setValue(prev => prev + 1);
}, 10);
return () => clearInterval(interval);
}, []);
return <ExpensiveComponent data={value} />;
}
Web Workers方案
将计算密集型任务转移到Worker线程:
// worker.js
self.onmessage = function(e) {
const result = heavyComputation(e.data);
self.postMessage(result);
};
// 主线程
const worker = new Worker('worker.js');
worker.onmessage = (e) => {
this.setState({ data: e.data });
};
// 触发计算
worker.postMessage(inputData);
Canvas渲染优化
对于可视化数据,Canvas比DOM更高效:
class Graph extends React.Component {
canvasRef = React.createRef();
componentDidUpdate() {
this.draw();
}
draw = () => {
const ctx = this.canvasRef.current.getContext('2d');
ctx.clearRect(0, 0, 800, 600);
// 绘制逻辑
this.props.data.forEach(point => {
ctx.fillRect(point.x, point.y, 5, 5);
});
};
render() {
return <canvas ref={this.canvasRef} width={800} height={600} />;
}
}
不可变数据与性能
使用Immutable.js减少不必要的渲染:
import { Map } from 'immutable';
class App extends React.Component {
state = {
data: Map({ count: 0 })
};
handleUpdate = () => {
this.setState(prev => ({
data: prev.data.update('count', c => c + 1)
}));
};
shouldComponentUpdate(nextProps, nextState) {
return !this.state.data.equals(nextState.data);
}
render() {
return <div>{this.state.data.get('count')}</div>;
}
}
虚拟列表实现
处理大数据量渲染:
class VirtualList extends React.Component {
state = {
startIndex: 0,
endIndex: 20
};
containerRef = React.createRef();
handleScroll = () => {
const { scrollTop } = this.containerRef.current;
const itemHeight = 50;
const startIndex = Math.floor(scrollTop / itemHeight);
this.setState({
startIndex,
endIndex: startIndex + 20
});
};
render() {
const { data } = this.props;
const { startIndex, endIndex } = this.state;
const visibleItems = data.slice(startIndex, endIndex);
return (
<div
ref={this.containerRef}
style={{ height: '500px', overflow: 'auto' }}
onScroll={this.handleScroll}
>
<div style={{ height: `${data.length * 50}px` }}>
{visibleItems.map((item, index) => (
<div key={startIndex + index} style={{ height: '50px' }}>
{item}
</div>
))}
</div>
</div>
);
}
}
React并发模式实验
使用Suspense和useTransition管理优先级:
function App() {
const [resource, setResource] = useState(null);
const [startTransition, isPending] = useTransition({ timeoutMs: 2000 });
const fetchData = () => {
startTransition(() => {
setResource(fetchDataAsync());
});
};
return (
<div>
<button onClick={fetchData} disabled={isPending}>
{isPending ? '加载中...' : '加载数据'}
</button>
<Suspense fallback={<Spinner />}>
<DataDisplay resource={resource} />
</Suspense>
</div>
);
}
实际案例分析
某金融交易平台遇到实时报价卡顿问题:
- 原始方案:每个报价变动触发完整DOM更新
- 问题表现:在2000+行数据时帧率降至10FPS
- 优化方案:
- 改用Canvas渲染价格变动
- Web Workers处理数据聚合
- 虚拟列表只渲染可视区域
- 优化结果:帧率稳定在60FPS,CPU占用降低70%
// 优化后的报价组件
class PriceCell extends React.PureComponent {
canvasRef = React.createRef();
componentDidUpdate(prevProps) {
if (prevProps.price !== this.props.price) {
this.drawPriceChange();
}
}
drawPriceChange = () => {
const ctx = this.canvasRef.current.getContext('2d');
// 绘制逻辑...
};
render() {
return <canvas ref={this.canvasRef} width={100} height={20} />;
}
}
性能监控与度量
建立性能基准的方法:
// 使用Performance API测量
function measureRender() {
performance.mark('start');
// 触发渲染
setState({...});
setTimeout(() => {
performance.mark('end');
performance.measure('render', 'start', 'end');
const measures = performance.getEntriesByName('render');
console.log(`渲染耗时: ${measures[0].duration}ms`);
}, 0);
}
// React Profiler API
<Profiler
id="Counter"
onRender={(id, phase, actualTime) => {
console.log(`${id} ${phase}耗时: ${actualTime}ms`);
}}
>
<Counter />
</Profiler>
浏览器渲染管线深度
理解完整渲染流程对优化的帮助:
- JavaScript执行 → 2. 样式计算 → 3. 布局 → 4. 绘制 → 5. 合成
高频
setState
导致1-3阶段反复执行,跳过某些阶段可提升性能:
// 使用will-change提示浏览器
.optimized {
will-change: transform;
transform: translateZ(0);
}
// 强制触发硬件加速
const style = {
transform: 'translate3d(0, 0, 0)'
};