React自定义MultiSelect组件useEffect更新状态触发无限循环求解
问题根因
- 你的Form组件每次重渲染时,
convertToMultiSelectOptions(clientsEntity)都会生成新的数组引用,即使clientsEntity内容未发生变化,传给MultiSelect的data属性也会被判定为更新。 - MultiSelect中通过监听
options变化的useEffect触发onChange,只要options的引用发生变化就会执行回调,而onChange触发的dispatch会更新Form状态,导致Form重渲染,再次传入新的data引用,若你内部有同步data到options的逻辑,就会形成无限循环。 - 你在Form中传给MultiSelect的
onChange是内联箭头函数,每次Form重渲染都会生成新的函数实例,会引发不必要的组件重渲染。
修复方案
1. 缓存Form层的转换后数据与回调函数
使用useMemo缓存转换后的下拉选项,使用useCallback缓存 onChange 回调,避免非必要的引用变更:
const Form: React.FunctionComponent<FormProps> = ({navigation}) => { const [clientsEntity, setClientsEntity] = useState([] as Client[]); const [state, dispatch] = useReducer(reducer, filterInitialState); // 缓存转换后的选项,仅clientsEntity变化时才重新计算 const selectOptions = useMemo(() => { return convertToMultiSelectOptions(clientsEntity) }, [clientsEntity]) // 缓存onChange回调,仅dispatch变化时才生成新函数 const handleClientChange = useCallback((clients: IItemData[]) => { console.log("onChange clients: ", clients); dispatch({type: 'clients', payload: clients}); }, [dispatch]) return ( <MultiSelect data={selectOptions} defaultAllChecked entityName="Clients" entityGenre="M" touchableRippleStyle={{margin: 15}} onChange={handleClientChange} /> ) }
2. 调整MultiSelect的回调触发逻辑
删掉监听options触发onChange的useEffect,改为在用户操作修改选项状态的回调中直接触发onChange;如果需要同步外部传入的data变更到内部状态,增加浅比较判断,避免非必要的状态更新:
// 数组浅比较工具函数,可根据实际数据结构调整判断逻辑 const areOptionsEqual = (a: IItemData[], b: IItemData[]) => { if (a.length !== b.length) return false return a.every((item, idx) => item.id === b[idx].id && item.checked === b[idx].checked) } const Multiselect: React.FunctionComponent<IMultiSelectProps> = ({ data = [], touchableRippleStyle, entityName = "Item", entityGenre = "M", onChange, defaultAllChecked = false}) => { const _data = convertToItemData(data, defaultAllChecked); const [menuState, setMenuState] = useState(false); const [options, setOptions] = useState(_data as IItemData[]); // 同步外部data变更到内部状态,仅内容真的变化时才更新 useEffect(() => { const newData = convertToItemData(data, defaultAllChecked) if(!areOptionsEqual(newData, options)) { setOptions(newData) } }, [data, defaultAllChecked]) // 示例:用户切换单个选项的回调,在修改状态后直接触发onChange const toggleOption = (optionId: string) => { const newOptions = options.map(item => item.id === optionId ? {...item, checked: !item.checked} : item ) setOptions(newOptions) onChange(newOptions) } // 全选/取消全选的回调同理,修改完options直接调用onChange即可 })
内容的提问来源于stack exchange,提问作者Ellysson Miike
相关产品推荐
相关产品推荐

