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

Kendo-React表格虚拟滚动与行选择功能兼容问题求助

Kendo React Grid 虚拟滚动与行选择(单选+全选)兼容方案

问题分析

当前实现存在几个核心问题,导致虚拟滚动和行选择无法正常兼容:

  1. 虚拟滚动模式下,data传递了完整的dataState映射数组,破坏了虚拟滚动仅渲染视口内数据的优化逻辑,同时每次选择状态变化都会重新遍历全量数据,性能低下。
  2. onSelectionChange依赖selectedState,导致每次选择变化都会重新创建回调函数,引发不必要的组件重渲染。
  3. 全选逻辑基于当前dataState判断,但虚拟滚动下dataState通常是分页加载的部分数据,无法正确反映全部数据的选中状态。

解决方案

1. 恢复虚拟滚动的正确数据传递

虚拟滚动模式下,Grid仅需要当前视口范围内的数据,直接传递分页后的切片数据即可,无需给每个数据项附加选中字段。

2. 优化选中状态管理

使用函数式更新维护selectedState,减少useCallback的依赖项,避免不必要的重渲染。

3. 修正全选逻辑

新增selectAll状态区分“全选当前页”和“选中全部”,同时在表头选择时处理两种场景。

修改后的完整代码

const [selectedState, setSelectedState] = React.useState({});
const [selectAll, setSelectAll] = React.useState(false);

const onSelectionChange = React.useCallback(
  (event) => {
    // 使用函数式更新,避免依赖selectedState
    setSelectedState(prevState => 
      getSelectedState({
        event,
        selectedState: prevState,
        dataItemKey: DATA_ITEM_KEY,
      })
    );
    // 手动选择行时取消全选状态
    setSelectAll(false);
  },
  [] // 空依赖数组,确保回调不会频繁重建
);

const onHeaderSelectionChange = React.useCallback(
  (event) => {
    const isSelectAll = event.value;
    setSelectAll(isSelectAll);
    
    if (isSelectAll) {
      // 全选当前页数据
      const newSelectedState = {...selectedState};
      dataState.slice(page.skip, page.skip + pageSize).forEach(item => {
        const id = idGetter(item);
        newSelectedState[id] = true;
      });
      setSelectedState(newSelectedState);
    } else {
      // 取消选中当前页数据
      const newSelectedState = {...selectedState};
      dataState.slice(page.skip, page.skip + pageSize).forEach(item => {
        const id = idGetter(item);
        delete newSelectedState[id];
      });
      setSelectedState(newSelectedState);
    }
  },
  [selectedState, page.skip, pageSize, dataState]
);

// 自定义行选中状态判断
const isRowSelected = (item) => {
  const id = idGetter(item);
  return selectAll || !!selectedState[id];
};

return (
  <Grid
    style={{ height: "60vh" }}
    // 虚拟滚动仅传递当前页数据
    data={dataState.slice(page.skip, page.skip + pageSize)}
    pageSize={pageSize}
    total={total}
    skip={page.skip}
    scrollable={'virtual'}
    rowHeight={50}
    onPageChange={pageChange}
    cellRender={loadingCell}
    dataItemKey={DATA_ITEM_KEY}
    selectable={{
      enabled: true,
      drag: false,
      cell: false,
      mode: "multiple",
    }}
    onSelectionChange={onSelectionChange}
    onHeaderSelectionChange={onHeaderSelectionChange}
    // 自定义行选中状态,无需依赖selectedField
    rowProps={(item) => ({
      selected: isRowSelected(item)
    })}
  >
    <GridColumn
      locked={true}
      width="50"
      // 表头选中状态:当前页所有行都被选中时为true
      headerSelectionValue={
        dataState.slice(page.skip, page.skip + pageSize).every(item => isRowSelected(item))
      }
      // 自定义选择列单元格
      cell={(props) => (
        <input
          type="checkbox"
          checked={isRowSelected(props.dataItem)}
          onChange={(e) => {
            const id = idGetter(props.dataItem);
            setSelectedState(prev => ({
              ...prev,
              [id]: e.target.checked
            }));
            setSelectAll(false);
          }}
        />
      )}
    />
    <GridColumn
      field="Action"
      width="102"
      locked={true}
      cell={MyCustomCell}
    />
    <GridColumn field="Index" title="Index" width="75" />
    <GridColumn field="stone_id" title="Stone ID" width="75" />
    <GridColumn field="cert_no" title="Certificate No" width="105" />
  </Grid>
);

关键修改点说明

  • 虚拟滚动数据传递:恢复data={dataState.slice(page.skip, page.skip + pageSize)},确保Grid只渲染当前视口的行,维持虚拟滚动的性能优势。
  • 选中状态优化:onSelectionChange使用函数式更新setSelectedState(prevState => ...),移除对selectedState的依赖,避免回调频繁重建。
  • 全选逻辑修正:新增selectAll状态,表头选择时仅操作当前页的选中状态,同时通过isRowSelected函数统一判断行选中状态。
  • 自定义选择列:手动实现选择列的复选框,确保选中状态正确同步,无需给数据项附加选中字段。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 14:24:54