React父组件状态变更时Picker的optGroups常量prop被识别为变更引发无限循环
问题原因
- optGroups引用不稳定:你在使用Picker组件时,直接把
optGroups的数组字面量写在了组件属性里,没有做引用稳定化处理。每次父组件因为formState更新触发重渲染时,都会重新生成一个全新的数组对象,哪怕内容完全没变,引用地址也发生了变化,导致Picker组件内部监听optGroups的useEffect被触发。 - useEffect存在强制触发更新的逻辑:useEffect在设置完默认值后,主动通过
dispatchEvent派发了input事件,这个事件会触发onInputValueChange回调也就是formDataHandler,再次修改父组件的formState,触发父组件重渲染,形成闭环循环。
解决方案
方案1:稳定optGroups的引用(最推荐,改动最小)
把作为常量的optGroups从组件属性内联写法改为组件外常量,或者用useMemo包裹,保证引用不会随父组件重渲染变化:
// 方式1:直接在父组件外部定义常量,全局唯一引用 const cashFlagOptGroups = [ { options: [ { value: 'true', label: 'دارد', selected: true }, { value: 'false', label: 'ندارد' }, ] } ] // 父组件内使用时直接传常量 <Picker emptyOpt={false} label="تخفیف نقدی" name="cashFlag" optGroups={cashFlagOptGroups} onInputValueChange={formDataHandler} /> // 方式2:如果optGroups依赖父组件状态,用useMemo包裹 const cashFlagOptGroups = useMemo(() => [ { options: [ { value: 'true', label: 'دارد', selected: true }, { value: 'false', label: 'ندارد' }, ] } ], [/* 这里放真正会导致optGroups内容变化的依赖 */])
方案2:优化Picker组件内部useEffect逻辑
去掉useEffect中不必要的input事件派发,只有初始挂载时设置一次默认值,不需要每次optGroups引用变化都触发父组件更新:
// 修改useEffect,仅设置内部默认值,不主动派发事件通知父组件 useEffect(() => { const selectedValuesArr = [...optGroups].map(optGroup => optGroup.options .filter(opt => opt.selected ?? false) .map(sOPT => sOPT.value)) .reduce((pv, cv) => [...pv, ...cv], []); const defValue = selectedValuesArr[0] ?? ''; setPickerValue(defValue); // 删掉下面这行主动派发input事件的代码,初始值不需要通知父组件,只有用户操作才需要触发回调 // setTimeout(() => pickerRef.current?.dispatchEvent(new Event('input', { bubbles: true })), 2); }, [optGroups, name]);
另外也可以把Picker组件改为标准受控组件,内部不维护pickerValue状态,直接由父组件通过props传入value,彻底避免内部状态和父状态不同步的问题。
方案3:使用深比较监听optGroups变化
如果业务上确实需要监听optGroups的内容变化而非引用变化,可以用深比较版本的useEffect代替原生useEffect:
// 可以引入第三方工具库的深比较useEffect,也可以自己实现深比较逻辑 import { useDeepCompareEffect } from 'react-use'; useDeepCompareEffect(() => { // 原有逻辑 }, [optGroups, name]);
这样只有optGroups的内容真的发生变更时,才会执行回调逻辑,不会因为引用变化误触发。
内容的提问来源于stack exchange,提问作者Iman Emadi
相关产品推荐
相关产品推荐

