React调和过程中组件是否必重渲染?相关实现疑问咨询
React渲染与调和过程解析及疑问解答
示例代码
import React from "react"; export default function App() { console.log("App is rerendered") const [time, setTime] = React.useState(0) React.useEffect(() => { const lol = setTimeout(() => setTime(prev => prev + 1), 100) return () => clearTimeout(lol) }, [time] ) function ShowTime() { console.log("ShowTime is rerended") return ( <div> Time : {time / 10} sec</div> ) } function ShowButton() { console.log("ShowButton is rerended") return ( <button onClick={() => console.log("I'm hard to click cuz rerendered the whole time :/")}> Button created with ShowButton component </button> ) } return ( <main> <ShowTime /> <ShowButton /> </main> ) }
我对代码执行过程的理解
- React创建包含App元素、ShowTime元素和ShowButton元素的虚拟DOM
- 首次渲染时,React渲染所有内容,创建App实例,其包含一个main DOM元素,以及ShowTime和ShowButton的实例
- 100ms后,App中的time状态发生变化
- React结合time状态的变化再次更新虚拟DOM
- 触发重渲染,进入调和阶段
基于React文档的理解
当组件更新时,实例保持不变(...)。接下来,会调用render()方法(...)
- React不会纠结App本身有没有变化,只要在虚拟DOM中遇到组件,提交阶段就会保留实例并执行App.render(),这里因为time状态变化,这个行为是合理的
- 对子组件执行递归调和过程
- 同理,React也不会纠结ShowTime和ShowButton有没有变化,只要是组件就保留实例,执行它们的render()方法
我的疑问
- 我对调和过程中组件相关逻辑的理解是否正确?
- 当父组件需要渲染时,无论子组件是否关联到props或状态变化,都会被渲染吗?比如示例中的ShowButton组件,这导致按钮难以点击,是否合理?
解答
关于调和过程的理解是否正确?
你的理解大体方向是对的,但有个关键细节需要修正:
因为ShowTime和ShowButton是定义在App组件内部的函数组件,每次App重渲染时,这两个函数都会被重新创建,相当于全新的组件类型(函数引用发生了变化)。这时候React不会保留旧的子组件实例,而是直接销毁旧实例、创建新实例,这也是它们每次都会打印"rerended"的核心原因之一。
除此之外的逻辑是正确的:父组件状态变化触发重渲染后,会进入调和阶段,类型匹配的组件实例会被保留,并重新执行渲染逻辑生成新的虚拟DOM,再递归处理子组件。
父组件渲染时子组件一定会跟着渲染吗?这种情况合理吗?
默认情况下是的,父组件重渲染时,所有直接或间接子组件都会跟着重渲染,不管子组件有没有用到变化的状态或props。
示例里ShowButton难以点击完全不合理,这是代码写法导致的问题:App每100ms就重渲染一次,加上ShowButton是内部定义的组件,每次都会被重新创建,对应的DOM元素也会频繁销毁重建,按钮一直在刷新自然很难被点击。
解决这个问题可以这么做:
- 把ShowTime和ShowButton移到App组件外部定义,这样它们的函数引用不会每次变化,React会识别出是同一个组件,ShowButton没有依赖任何props,只会渲染一次。
- 用
React.memo包裹ShowButton,让React对比前后props,没有变化就跳过重渲染(更推荐第一种方案)。 - 优化定时器逻辑:把useEffect的依赖改成空数组,用setInterval代替嵌套的setTimeout,减少不必要的重渲染次数。
内容的提问来源于stack exchange,提问作者Fabien
相关产品推荐
相关产品推荐

