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

React MUI虚拟化Autocomplete选中选项后滚动回顶部问题求助

React MUI Autocomplete 多选时虚拟化列表滚动回顶部问题修复

问题描述

我在使用React MUI的Autocomplete组件,因为下拉列表需要承载数千条数据,所以集成了react-window实现虚拟化。虚拟化功能正常,但添加onChange函数后,多选时选中选项下拉列表会自动滚动回顶部。尝试过将ListboxComponent用useCallback包裹,但问题依然存在。

核心原因

  1. useResetCache钩子在itemCount变化时调用resetAfterIndex(0, true),强制滚动到顶部。而多选时Autocomplete会重新渲染选项列表,触发这个钩子。
  2. ListboxComponent的useCallback依赖数组为空,但内部用到了renderRow和LISTBOX_PADDING,导致不必要的重渲染。

修复方案

1. 调整缓存重置的触发条件

修改useResetCache钩子,仅在分组切换(数据真正更新)时重置缓存,而非每次选项变化:

function useResetCache() {
  const ref = React.useRef<VariableSizeList>(null);
  React.useEffect(() => {
    if (ref.current != null) {
      ref.current.resetAfterIndex(0, true);
    }
  }, [selectedGroup]); // 仅在分组切换时重置缓存
  return ref;
}

2. 稳定renderRow函数

用useCallback包裹renderRow,避免每次渲染生成新函数触发重渲染:

const renderRow = React.useCallback((props: ListChildComponentProps) => {
  const { data, index, style } = props;
  const dataSet = data[index];
  const inlineStyle = {
    ...style,
    top: (style.top as number) + LISTBOX_PADDING,
  };
  const option = dataSet[1] as Product;

  return (
    <li {...dataSet[0]} style={inlineStyle}>
      <Grid container spacing={1}>
        <Grid xs={12}>
          <Typography variant="subtitle2">{option.new_idprodotto}</Typography>
        </Grid>
        <Grid xs={12}>
          <Typography variant="body2">
            {option.new_name} -{" "}
            {
              option[
                "_nk_lineaprodotto_value@OData.Community.Display.V1.FormattedValue"
              ]
            }
          </Typography>
        </Grid>
      </Grid>
    </li>
  );
}, [LISTBOX_PADDING]);

3. 修正ListboxComponent的依赖数组

把renderRow和LISTBOX_PADDING加入useCallback的依赖数组,确保组件仅在必要时重渲染:

const ListboxComponent = React.useCallback<
  React.ForwardRefRenderFunction<
    HTMLDivElement,
    React.HTMLAttributes<HTMLElement>
  >
>(
  function ListboxComponent(props, ref) {
    const { children, ...other } = props;
    const itemData: React.ReactNode[] = [];
    (children as React.ReactElement[]).forEach(
      (item: React.ReactElement & { children?: React.ReactElement[] }) => {
        itemData.push(item);
        itemData.push(...(item.children || []));
      }
    );

    const itemCount = itemData.length;
    const itemSize = 55;

    const getChildSize = () => {
      return itemSize;
    };

    const getHeight = () => {
      if (itemCount > 8) {
        return 8 * itemSize;
      }
      return itemData.map(getChildSize).reduce((a, b) => a + b, 0);
    };

    const gridRef = useResetCache(); // 不再传递itemCount参数

    return (
      <div ref={ref}>
        <OuterElementContext.Provider value={other}>
          <VariableSizeList
            itemData={itemData}
            height={getHeight() + 2 * LISTBOX_PADDING}
            width="100%"
            ref={gridRef}
            outerElementType={OuterElementType}
            innerElementType="ul"
            itemSize={() => itemSize}
            overscanCount={5}
            itemCount={itemCount}
          >
            {renderRow}
          </VariableSizeList>
        </OuterElementContext.Provider>
      </div>
    );
  },
  [renderRow, LISTBOX_PADDING] // 添加必要依赖
);

效果说明

修改后,多选时只会更新选中状态,不会触发不必要的滚动重置,同时避免了组件无意义的重渲染,彻底解决滚动回顶部的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 21:46:29