前两天线上的推荐卡片出现了一个神奇的bug,就是一旦你加载下一列,所有的推荐卡片都会闪烁。线上情况类似如下:
大胆猜测,闪烁是因为子组件卸载了,所以在子组件加入如下代码测试:
useEffect(() => {
return () => {
console.log("unmount");
}
}, []);
果然,发生了卸载,开始排查代码,发现代码中有类似如下代码:
const Test = function () {
const [update, setUpdate] = useState(false);
useEffect(() => {
const timeSign = setInterval(() => {
setUpdate(oldState => !oldState)
}, 600);
return () => {
clearInterval(timeSign);
}
}, []);
// 子组件
const ChildrenComponent = () => {
useEffect(() => {
return () => {
console.log("unmount");
}
}, []);
return <img className="product-img" src="https://img.mrvcdn.com/g/fb/kf/Eee800a9c74584f758b617c8c446a089b2.jpg_2200x2200q75.avif" />
}
return <ChildrenComponent />
};
居然在组件中声明了组件,导致每次父组件更新,子组件都会卸载并且重新挂载。当然这为什么会造成子组件卸载,这就要结合react的diff算法来说了,react的diff算法过程如下:
-
相同类型的节点:
- 如果两个节点类型相同(都是元素节点或都是文本节点),则递归比较它们的属性和子节点。
- 对于元素节点,会检查并更新属性(props),然后继续比较子节点。
- 对于文本节点,直接更新文本内容。
-
不同类型的节点:
- 如果节点类型不同,则 React 会认为它们是完全不相同的节点,直接替换整个子树。旧节点会被移除,新节点会重新创建并插入。
-
区分 key 属性:
- React 利用 key 属性来高效处理子节点的重排和复用。
- 如果子节点有 key 属性,React 会基于 key 进行对比和复用。这样做可以避免不必要的节点删除和创建。
- React 会为每个子节点生成一个映射表,以 key 为索引来快速查找匹配的节点。
-
递归处理子节点(树的遍历):
- 基于上述步骤,React 会递归地处理每个节点及其子节点,进行深度优先遍历(DFS)的对比和更新。
我们直接看第二条,当type不同时,react会移除并且重建dom,对应到我们项目中就是,每次父组件更新时,父组件内部声明的子组件都会重新被声明,导致type不同,也就是为什么子组件会一直卸载的原因。所以一般来说不要在组件当中声明组件,会导致奇怪的问题。
当然,如果你真的想在组件中声明并使用组件,那就需要使用useCallback,并且依赖设置为[],让返回的函数保持为同一个:
可以发现已经不闪烁了。但是此时子组件不能引用父组件的变量,否则会出现一些更新问题,假如我们在子组件里引用了父组件的变量count,并尝试更新:
可以发现count一直为0,这主要是函数实例和作用域的问题,这里就不多说了,有兴趣可以看一下这篇文章#别再说react闭包问题是react的错了