在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
相关产品推荐
相关产品推荐

