React子组件在Props更新时未触发重新渲染问题排查
核心根因
你遇到的问题本质是异步请求时序错位+状态直接突变共同导致的,具体拆解为3个问题:
- 父组件
findHierarchy方法异步逻辑完全错误:DuplicateChecker是异步接口,你在同步逻辑中直接判断element?.hierarchy?.length时,接口还未返回,hierarchy根本没有被赋值,findHierarchy返回的dupe永远是初始值false。后续接口返回后你直接修改duplicationSearchParam原数组的元素属性,属于直接突变React状态,React无法检测到这个变更,也不会触发confirmationProps的二次更新,子组件拿到的永远是第一次同步赋值的空hierarchy数据。你控制台能看到值是因为浏览器控制台对象打印是懒加载的,你展开时数组已经被你后续异步操作修改了,实际React状态中存储的还是旧值。 next函数闭包陷阱+异步逻辑错误:你在next中获取的confirmationProps是点击按钮那一刻的闭包旧值,就算父组件状态后续更新,这个闭包里的值也不会变化。你用普通变量inProgress作为并发锁,每次组件渲染都会重置这个变量,锁完全不生效。Promise.all([confirmationProps, duplicationSearchParam])的写法完全无效,这两个是普通变量不是Promise,不会等待任何异步操作完成。- 子组件依赖不完整+逻辑错误:
useEffect只监听props.duplicateData.responseHierarchy,但内部用到的props.duplicate、props.rawEdit.id、buildDuplicate都没有加入依赖数组,可能出现逻辑执行异常。判断dupeData !== []永远为真,数组是引用类型,两个不同数组的比较永远返回false,这个判断逻辑完全无效。
修复方案
第一步:修复父组件异步逻辑
把findHierarchy改成全异步,等所有DuplicateChecker请求返回后再更新状态,禁止突变原数组:
useEffect(() => { const findHierarchy = async () => { // 先去重 const uniqueParams = duplicationSearchParam.filter( (object, index) => index === duplicationSearchParam.findIndex( (obj) => JSON.stringify(obj.name) === JSON.stringify(object.name) ) ); // 等待所有异步请求完成,返回新的对象数组,不修改原数组 const processedParams = await Promise.all( uniqueParams.map(async (element) => { const data = await DuplicateChecker(element.name); // 返回新对象,不突变原元素 return { ...element, hierarchy: data.status > 200 ? [] : data }; }) ); // 所有请求返回后再判断是否有重复 const hasDuplicate = processedParams.some(item => item.hierarchy?.length > 0); // 更新状态 setConfirmationProps({ retrievedData: formData, duplicate: hasDuplicate, responseHierarchy: processedParams, }); }; findHierarchy(); }, [duplicationSearchParam, formData]);
第二步:修复next函数逻辑
- 用
useRef存储inProgress锁,避免渲染重置 - 把所有fetch请求用
Promise.all包裹,等待全部请求完成后再操作 - 不要依赖闭包中的旧状态,如果需要等
confirmationProps更新完成后再跳转,可以新增useEffect监听跳转触发条件,或者把接口请求和状态更新逻辑串成串行异步流
// 先在组件顶层声明锁 const inProgressRef = useRef(false); const next = async (data) => { if (inProgressRef.current) return; inProgressRef.current = true; try { // 等待所有国家查询请求完成 const countryLabels = await Promise.all( formData.addresses?.map(async (address) => { const res = await fetch(`/api/ref/country/${address?.country}`); return res.label; }) || [] ); const clean = MapCleanse(data, countryLabels); const cleanseRes = await fetch("/api/v1/organization/cleanse", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(clean), }); if (cleanseRes.status > 200) { console.log(cleanseRes.message); message.error(getErrorCode(cleanseRes.message.toString())); return; } setCleansed(cleanseRes); // 如果要确保拿到最新的confirmationProps,可以加额外的状态监听触发跳转 setCurrent(prev => prev + 1); } catch (err) { console.log(err); } finally { inProgressRef.current = false; } };
第三步:修复子组件逻辑
- 补全useEffect的所有依赖,建议开启ESLint的react-hooks规则自动校验补全
- 修正无效的数组比较逻辑
useEffect(() => { console.log(props.duplicate); console.log(props.duplicateData); console.log(props.confirmationProps); let newArray = props.duplicateData.filter(Boolean); let duplicateCheck = newArray.map((checker) => checker?.hierarchy?.find((Qstring) => Qstring?.highlightedId?.includes(UUIDToString(props?.rawEdit?.id)) ) ).filter(Boolean); console.log(newArray, "new array"); console.log(duplicateCheck, "duplicate check"); if (newArray?.length > 0 && duplicateCheck?.length === 0) { let list = []; newArray.forEach((dupeData) => { // 修正无效判断 if (!dupeData?.hierarchy?.length) return; let clean = dupeData.hierarchy?.filter( (hierarchy) => !hierarchy.queryString ); let queryParam = dupeData.hierarchy?.filter( (hierarchy) => hierarchy.queryString ); setSelectedKeys([queryParam?.[0]?.highlightedId]); if (clean?.length > 0) { console.log("clean", clean); clean.forEach((item) => { const treeNode = buildDuplicate(item); list.push(treeNode); }); setCleansedTree([...list]); setTreeDuplicate(true); } else { setTreeDuplicate(false); } }); } // 补全所有依赖 }, [props.duplicate, props.duplicateData, props.rawEdit?.id, buildDuplicate]);
内容的提问来源于stack exchange,提问作者wowza_MAN
相关产品推荐
相关产品推荐

