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

React父组件重渲染时子组件状态丢失,求问题原因

问题:React组件选中状态在API参数更新后丢失

我写了一个GenericFilter组件,基于API返回数据生成复选框列表,包含以下逻辑:

  • filters状态:通过useEffect调用API获取可用过滤器列表
  • selected状态:存储用户选中的过滤器对象
  • onChange属性:选中状态变化时触发回调

组件代码:

export const GenericFilter = ({
  apiUrl,
  apiParams,
  onChange = () => {},
}: GenericFilterProps) => {
  const [filters, setFilters] = useState<Filter[]>([]);
  const [selected, setSelected] = useState<Filter[]>([]);

  useEffect(() => {
    axios.get(apiUrl, { params: apiParams }).then(res => setFilters(res.data));
  }, [apiUrl, apiParams]);

  return (
    <>
      {filters.map(filter =>
        <div key={filter.id}>
          <label>{filter.name} ({filter.count})</label>
          <input
            type='checkbox'
            checked={selected.includes(filter)}
            disabled={!filter.enabled}
            onChange={({ target: { checked } }) => {
              const selection = (checked ? [...selected, filter] : selected.filter(f => f !== filter));
              setSelected(selection);
              onChange(selection);
            }}
          />
        </div>
      )}
    </>
  );
}

父组件FiltersPanel使用该组件,操作后出现问题:

  1. 勾选复选框后,选中状态能正常保留
  2. 点击按钮调用setSizeParams更新参数时,GenericFilter重新渲染,选中状态完全丢失

父组件代码:

export const FiltersPanel= () => {
  const [sizeParams, setSizeParams] = useState({});

  return (
    <>
      <button className='btn btn-primary' onClick={() => {
        setSizeParams(prev => ({ ...prev, time: Math.random() }));
      }}>Update size params</button>

      <GenericFilter apiUrl='/api/filter/size' apiParams={sizeParams} />
    </>
  );
};

原因分析

  • 对象引用不匹配:当apiParams更新时,useEffect重新调用API获取新的filters数组,新数组中的每个filter都是API返回的全新对象实例。而你用selected.includes(filter)判断选中状态,这是基于对象引用的全等比较(===),新的filter对象和之前存在selected数组里的旧对象引用完全不同,所以判断结果为false,选中状态丢失。
  • 选中状态未与新数据关联:selected状态存储的是旧的filter对象引用,当新的filters数组加载后,没有逻辑将旧的选中状态映射到新的对象上。

解决方法

方案1:用唯一标识(如id)跟踪选中状态(推荐)

放弃存储filter对象,改为存储选中项的唯一id,用id判断选中状态,彻底避免对象引用问题:

修改后的GenericFilter代码:

export const GenericFilter = ({
  apiUrl,
  apiParams,
  onChange = () => {},
}: GenericFilterProps) => {
  const [filters, setFilters] = useState<Filter[]>([]);
  // 改为存储选中的id数组
  const [selectedIds, setSelectedIds] = useState<number[]>([]);

  useEffect(() => {
    axios.get(apiUrl, { params: apiParams }).then(res => setFilters(res.data));
  }, [apiUrl, apiParams]);

  // 可选:当filters更新时,过滤掉已不存在的id(比如API返回的过滤器列表有删除项)
  useEffect(() => {
    const existingIds = new Set(filters.map(f => f.id));
    setSelectedIds(prev => prev.filter(id => existingIds.has(id)));
  }, [filters]);

  return (
    <>
      {filters.map(filter =>
        <div key={filter.id}>
          <label>{filter.name} ({filter.count})</label>
          <input
            type='checkbox'
            // 用id判断是否选中
            checked={selectedIds.includes(filter.id)}
            disabled={!filter.enabled}
            onChange={({ target: { checked } }) => {
              const newSelectedIds = checked 
                ? [...selectedIds, filter.id] 
                : selectedIds.filter(id => id !== filter.id);
              
              setSelectedIds(newSelectedIds);
              // 如果onChange需要返回Filter对象,从当前filters中匹配
              const selectedFilters = filters.filter(f => newSelectedIds.includes(f.id));
              onChange(selectedFilters);
            }}
          />
        </div>
      )}
    </>
  );
}

方案2:复用旧对象引用(局限性大)

如果确定API返回的filter仅更新属性(如count)、不新增/删除项,可在更新filters时复用旧对象的引用,保持选中状态的匹配:

useEffect(() => {
  axios.get(apiUrl, { params: apiParams }).then(res => {
    const newFilters = res.data.map(newFilter => {
      // 查找旧filters中同id的对象,复用引用并更新属性
      const existingFilter = filters.find(f => f.id === newFilter.id);
      return existingFilter ? { ...existingFilter, ...newFilter } : newFilter;
    });
    setFilters(newFilters);
  });
}, [apiUrl, apiParams, filters]);

此方案仅适用于API数据结构稳定的场景,一旦有新增/删除过滤器,容易出现状态不一致问题,优先选方案1。


内容的提问来源于stack exchange,提问作者gremo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 13:20:28