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

在Solid.js中如何通过信号更新引用来控制表格滚动?

在Solid.js中如何通过信号更新引用来控制表格滚动?

我明白你的需求了——不想用querySelector,而是通过Solid的响应式系统自动关联选中单元格的引用,进而控制表格滚动。下面是具体的解决方案,我们通过创建单元格的引用数组,结合响应式信号来实现这个功能:

1. 创建二维单元格引用数组

因为我们的表格是固定的10行10列,所以可以提前创建一个和行列对应的ref数组,每个单元格对应一个独立的ref,这样就能通过行/列索引直接定位到对应的DOM元素:

首先要导入createRef工具:

import { batch, Component, createEffect, createSelector, createSignal, For, JSX, createRef } from "solid-js";

然后初始化二维ref数组:

// 初始化二维ref数组,每个单元格对应一个ref
const cellRefs = rows.map(() => cells.map(() => createRef<HTMLDivElement>()));

2. 给每个单元格绑定对应的ref

在循环渲染单元格时,把对应的ref绑定到单元格元素上,这样每个单元格的DOM元素都会被存入对应的ref位置:

<For each={cells}>
  {(_, cellIndex) => (
    <div
      class="cell"
      classList={{ selected: isSelectedRow(rowIndex()) && isSelectedCell(cellIndex()) }}
      onMouseDown={onMouseDown(rowIndex(), cellIndex())}
      ref={cellRefs[rowIndex()][cellIndex()]} // 绑定当前单元格的ref
    >
      row {rowIndex()} - cell {cellIndex()}
    </div>
  )}
</For>

3. 优化鼠标点击事件处理

之前通过dataset解析索引的方式可以优化,直接在创建事件处理函数时传递行列索引,避免类型转换的潜在问题,代码也更简洁:

// 改写为工厂函数,直接接收行列索引
const onMouseDown = (rowIdx: number, cellIdx: number) => () => {
  batch(() => {
    setSelectedRowIndex(rowIdx);
    setSelectedCellIndex(cellIdx);
  });
};

4. 在响应式Effect中控制表格滚动

当选中的行或列索引变化时,Solid的createEffect会自动触发。我们通过索引从ref数组中获取对应的单元格元素,然后调用scrollIntoView让表格自动滚动到该元素的可见区域:

let tableRef!: HTMLDivElement;

createEffect(() => {
  const rowIdx = selectedRowIndex();
  const cellIdx = selectedCellIndex();
  // 通过索引获取选中单元格的DOM元素
  const selectedCell = cellRefs[rowIdx][cellIdx]();
  const tableEl = tableRef;

  if (selectedCell && tableEl) {
    // 让选中单元格滚动到表格的可见区域
    selectedCell.scrollIntoView({
      behavior: 'auto', // 可选'smooth'实现平滑滚动
      block: 'nearest', // 垂直方向优先让元素保持可见
      inline: 'nearest' // 水平方向优先让元素保持可见
    });
  }
});

完整修改后的组件代码

整合所有修改后的完整代码如下:

import { batch, Component, createEffect, createSelector, createSignal, For, JSX, createRef } from "solid-js";

export const DataGrid: Component = () => {
  const rows = Array.from({ length: 10 });
  const cells = Array.from({ length: 10 });

  const [selectedRowIndex, setSelectedRowIndex] = createSignal(0);
  const [selectedCellIndex, setSelectedCellIndex] = createSignal(0);

  const isSelectedRow = createSelector(selectedRowIndex);
  const isSelectedCell = createSelector(selectedCellIndex);

  // 初始化二维单元格ref数组
  const cellRefs = rows.map(() => cells.map(() => createRef<HTMLDivElement>()));

  const keysToPrevent = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];

  const onKeyDown: JSX.EventHandler<HTMLDivElement, KeyboardEvent> = (event) => {
    const { key, ctrlKey } = event;

    if (keysToPrevent.includes(key)) {
      event.preventDefault();
    }

    const rowLength = rows.length;
    const cellLength = cells.length;

    switch (key) {
      case "ArrowUp":
        if (ctrlKey) {
          setSelectedRowIndex(0);
        } else if (selectedRowIndex() > 0) {
          setSelectedRowIndex((v) => v - 1);
        }
        break;

      case "ArrowDown":
        if (ctrlKey) {
          setSelectedRowIndex(rowLength - 1);
        } else if (selectedRowIndex() < rowLength - 1) {
          setSelectedRowIndex((v) => v + 1);
        }
        break;

      case "ArrowLeft":
        if (ctrlKey) {
          setSelectedCellIndex(0);
        } else if (selectedCellIndex() > 0) {
          setSelectedCellIndex((v) => v - 1);
        }
        break;

      case "ArrowRight":
        if (ctrlKey) {
          setSelectedCellIndex(cellLength - 1);
        } else if (selectedCellIndex() < cellLength - 1) {
          setSelectedCellIndex((v) => v + 1);
        }
        break;
    }
  };

  // 优化后的鼠标点击事件
  const onMouseDown = (rowIdx: number, cellIdx: number) => () => {
    batch(() => {
      setSelectedRowIndex(rowIdx);
      setSelectedCellIndex(cellIdx);
    });
  };

  let tableRef!: HTMLDivElement;

  createEffect(() => {
    const rowIdx = selectedRowIndex();
    const cellIdx = selectedCellIndex();
    const selectedCell = cellRefs[rowIdx][cellIdx]();
    const tableEl = tableRef;

    if (selectedCell && tableEl) {
      selectedCell.scrollIntoView({
        behavior: 'auto',
        block: 'nearest',
        inline: 'nearest'
      });
    }
  });

  return (
    <div class="table" tabIndex={0} onKeyDown={onKeyDown} ref={tableRef}>
      <For each={rows}>
        {(_, rowIndex) => (
          <div class="row" classList={{ selected: isSelectedRow(rowIndex()) }}>
            <For each={cells}>
              {(_, cellIndex) => (
                <div
                  class="cell"
                  classList={{ selected: isSelectedRow(rowIndex()) && isSelectedCell(cellIndex()) }}
                  onMouseDown={onMouseDown(rowIndex(), cellIndex())}
                  ref={cellRefs[rowIndex()][cellIndex()]}
                >
                  row {rowIndex()} - cell {cellIndex()}
                </div>
              )}
            </For>
          </div>
        )}
      </For>
    </div>
  );
};

方案优势

  • 完全摒弃querySelector,所有DOM操作都通过Solid的ref系统完成,符合响应式编程最佳实践
  • 引用与行列索引直接绑定,信号变化时自动触发滚动逻辑,无需手动监听DOM
  • 鼠标事件处理更安全,避免了dataset类型转换的潜在问题

备注:内容来源于stack exchange,提问作者user30086561

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 20:15:30