为何useEffect依赖数组传入空对象会引发无限循环?原因分析与解决方案
先搞清楚循环的根源
React的useEffect是靠引用是否相等来判断依赖有没有变化的。你现在的dataForFilter是组件每次渲染时都会重新创建的对象——哪怕它是空的,或者内容和上一次完全一样,它在内存里的引用都是新的。
整个循环的流程是这样的:
- 组件渲染,生成新的
dataForFilter对象 useEffect检测到依赖的引用发生变化,重新执行fetchDatafetchData调用loadData,通常这会触发组件状态更新,导致组件再次渲染- 回到第一步,无限循环就此产生
哪怕初始时dataForFilter是空对象{},每次渲染都会生成一个全新的空对象,引用和上一次不一样,所以useEffect会一直被触发。
给你几个实用的解决方法
方法1:直接依赖原始数据,把对象生成逻辑放到useEffect内部
如果dataForFilter的来源只有defaultFormData[someKey]和props.route.params,那不如直接把这两个原始值作为useEffect的依赖,把对象的生成逻辑收拢到useEffect里:
useEffect(() => { async function fetchData() { // 在这里生成过滤参数 let dataForFilter = typeof defaultFormData[someKey] !== 'undefined' ? defaultFormData[someKey] : {}; // 复制一份原对象,避免直接修改原始数据 dataForFilter = { ...dataForFilter }; for (let key in props.route.params) { if (props.route.params.hasOwnProperty(key)) { dataForFilter[key] = props.route.params[key]; } } const body = { ...dataForFilter }; loadData(url, body); } fetchData(); }, [defaultFormData[someKey], props.route.params, url]);
这样只有当这些原始数据真的变化时,useEffect才会执行,不会因为对象引用变化而无效触发。
方法2:用useMemo缓存对象引用
如果一定要在组件顶层定义dataForFilter,可以用useMemo把它缓存起来,只有当依赖的原始数据变化时,才重新生成对象:
const dataForFilter = useMemo(() => { let temp = typeof defaultFormData[someKey] !== 'undefined' ? defaultFormData[someKey] : {}; // 复制原对象,避免修改原始数据 temp = { ...temp }; for (let key in props.route.params) { if (props.route.params.hasOwnProperty(key)) { temp[key] = props.route.params[key]; } } return temp; }, [defaultFormData[someKey], props.route.params]); useEffect(() => { async function fetchData() { const body = { ...dataForFilter }; loadData(url, body); } fetchData(); }, [dataForFilter, url]);
useMemo会帮你保留这个对象的引用,只有当defaultFormData[someKey]或props.route.params变化时,才会重新生成新的dataForFilter,这样useEffect就不会乱触发了。
方法3:如果对象结构固定,直接依赖具体键值
要是你明确知道dataForFilter里只会有几个固定的键(比如name、status这类),那可以直接把这些具体的值作为依赖,比依赖整个对象更精准:
// 假设dataForFilter里的键是name和status useEffect(() => { async function fetchData() { const dataForFilter = { ...(defaultFormData[someKey] || {}), ...props.route.params }; loadData(url, dataForFilter); } fetchData(); }, [defaultFormData[someKey]?.name, defaultFormData[someKey]?.status, props.route.params.name, props.route.params.status, url]);
这个方法适合过滤参数结构固定的场景,能最大程度避免不必要的触发。
小提醒
不管用哪种方法,都别直接修改defaultFormData[someKey]的原对象,一定要复制一份再修改,不然可能会触发意外的组件更新哦。
内容的提问来源于stack exchange,提问作者Petrik-a-dost

