含Props依赖的useEffect是否每次渲染都重跑?对象类型Props如何处理?
问题解答
1. 当roomId为对象类型时,useEffect会每次渲染都重执行吗?
会的。React判断useEffect依赖是否变化用的是浅比较——它只会对比引用是否相同,不会深入检查对象的内容。如果父组件每次渲染时传递的是新的对象(比如直接写roomId={{ id: 'room1' }}),哪怕对象里的键值完全没变,这个新对象的引用和上一次渲染的也不一样,React就会认为依赖发生了变化,触发effect重新执行。
2. 如何实现仅当对象键值变化时才执行effect?
这里有几种实用方案,按推荐优先级排序:
方案一:解构对象属性,直接依赖具体值
如果对象的结构简单,直接解构出需要的属性,把属性放进依赖数组。这样只要属性值没变,不管外层对象引用怎么变,effect都不会触发:
import { useEffect, useState } from 'react'; import { createConnection } from './chat.js'; function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // 解构出实际需要的属性 const { id } = roomId; useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [serverUrl, id]); // 依赖具体属性值 // ... }
方案二:在父组件用useMemo缓存对象引用
如果必须传递整个对象给子组件,可以在父组件用useMemo缓存对象,只有当对象内容的依赖变化时才生成新对象,保证子组件拿到的引用稳定:
// 父组件 function Parent() { const roomId = useMemo(() => ({ id: 'room1' }), []); // 空依赖,只生成一次 return <ChatRoom roomId={roomId} />; }
这样子组件的roomId引用始终不变,只有当父组件的useMemo依赖变化时,才会传递新的对象,effect才会重新执行。
方案三:自定义深比较的useEffect
如果对象结构复杂,不方便解构所有属性,可以自己实现一个基于深比较的effect hook。比如借助lodash的isEqual做深比较:
import { useEffect, useRef } from 'react'; import isEqual from 'lodash.isequal'; function useDeepCompareEffect(callback, dependencies) { const dependenciesRef = useRef(); // 只有当深比较发现依赖内容变化时,才更新引用 if (!isEqual(dependenciesRef.current, dependencies)) { dependenciesRef.current = dependencies; } // 用更新后的引用作为useEffect的依赖 useEffect(callback, [dependenciesRef.current]); } // 使用方式 function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useDeepCompareEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [serverUrl, roomId]); // 直接传对象,内部做深比较 // ... }
注意:深比较会带来额外的性能开销,所以只有在前面两种方案不适用时再考虑这个。
内容的提问来源于stack exchange,提问作者Magnus
相关产品推荐
相关产品推荐

