Fiber架构下的重新渲染过程
- 任务调度
- 任务拆分:Fiber将渲染任务拆分成一个个小的工作单元,即Fiber节点。每个Fiber节点代表一个React元素,包含了该元素的各种信息以及对应的副作用(如DOM更新)。这使得任务可以分段执行,不再是一口气完成整个渲染。
- 调度算法:使用requestIdleCallback(浏览器空闲时执行任务)或setTimeout(在requestIdleCallback不支持时作为兜底)来调度任务。这样可以优先执行高优先级任务,而将低优先级任务在浏览器空闲时段执行,避免阻塞主线程,保证页面的流畅性。
- 渲染优先级
- 同步任务:如用户交互(点击、输入等)产生的更新,这类任务优先级最高,需要立即处理以保证交互的实时响应。
- 生命周期任务:如
componentDidMount
、componentDidUpdate
等,优先级次之。
- 异步任务:数据获取后引起的更新等,优先级相对较低,可以在浏览器空闲时执行。
避免不必要重新渲染的高级优化策略
- 使用React.memo
- 原理:React.memo是一个高阶组件,用于对函数式组件进行浅比较优化。它会在组件接收到新的props时,对新旧props进行浅比较,如果props没有变化,则不会重新渲染该组件。
- 示例:
const MyComponent = React.memo((props) => {
return <div>{props.value}</div>;
});
- shouldComponentUpdate的优化使用
- 原理:对于类组件,
shouldComponentUpdate
方法允许开发者手动控制组件是否需要重新渲染。通过比较新旧props和state,开发者可以决定是否有必要触发重新渲染。
- 示例:
class MyClassComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.value!== nextProps.value || this.state.someValue!== nextState.someValue;
}
render() {
return <div>{this.props.value}</div>;
}
}
- Memoization(记忆化)
- 原理:通过缓存函数的计算结果,避免重复计算。在React中,可以使用
useMemo
和useCallback
钩子。useMemo
用于缓存值计算结果,useCallback
用于缓存函数,避免在每次渲染时重新创建函数,减少不必要的重新渲染。
- 示例:
const expensiveCalculation = (a, b) => {
// 复杂计算
return a + b;
};
const MyComponent = () => {
const result = useMemo(() => expensiveCalculation(1, 2), []);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return (
<div>
<p>{result}</p>
<button onClick={handleClick}>Click me</button>
</div>
);
};
在大型项目中从整体架构层面预防因不合理渲染导致的性能问题
- 合理的组件拆分
- 原则:将大型组件拆分成多个功能单一、职责明确的小组件。这样每个小组件的渲染逻辑更简单,依赖更少,减少因父组件更新导致不必要子组件重新渲染的概率。
- 示例:一个复杂的用户信息展示组件,可以拆分为头像组件、基本信息组件、联系方式组件等。
- 状态提升
- 原理:将共享状态提升到最近的共同祖先组件,通过props传递数据给子组件。这样可以避免多个子组件维护相同状态,减少不必要的状态更新和重新渲染。
- 示例:如果有多个子组件需要使用用户登录状态,将登录状态提升到父组件,通过props传递给需要的子组件。
- 使用Redux或MobX等状态管理库
- 原理:这些库提供了集中式的状态管理,通过严格的状态更新规则和订阅 - 发布模式,使得状态变化可控。并且可以利用其特性(如Redux的reducer纯函数特性),在状态变化时精确控制哪些组件需要重新渲染。
- 示例:在Redux中,通过定义reducer来处理状态变化,只有订阅了相关状态的组件才会在状态变化时重新渲染。