You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 12:54:04