- 前端
- UI组件
【免费下载链接】handsontable
JavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡
Handsontable 为整个公共 API 提供了开箱即用的 TypeScript 声明文件(.d.ts),本文是官方类型参考文档的完整展开版:涵盖支持的最低 TypeScript 版本与声明降级机制、handsontable与handsontable/base两个类型入口的选择、从配置类型到主题类型/Overlay 类型的全量类型清单,以及 settings 对象、hook 回调、自定义 renderer/editor、React/Angular/Vue 实例引用的实战写法。读完本文,你将能在自己的 Handsontable 项目中获得完整的类型检查与 IDE 自动补全,并理解这些声明在仓库中是如何生成与维护的。
支持的 TypeScript 版本
官方发布的.d.ts文件要求TypeScript 5.1 或更高版本。如果项目使用低于 5.1 的 TypeScript,请先升级 TypeScript 版本,再使用这些声明。
Handsontable 内部使用最新的 TypeScript 版本进行开发与编译(当前仓库的 handsontable/package.json 中开发依赖为typescript: "^6.0.0"),以获得最严格的内部类型检查和最新的语言特性。但发布出去的声明文件会被降级(downlevel)到 TS 5.1 兼容级别:构建时,脚本会把只存在于 TS 5.2+ 的类型(WeakKey、Disposable、AsyncDisposable)替换为 TS 5.1 等价物,把只存在于 TS 5.6+ 的类型(ArrayIterator、IteratorObject等)替换为对应的迭代器类型。
这一降级过程由 handsontable/scripts/downlevel-dts.mjs 实现,其注释明确写着:"The published .d.ts must be consumable by TypeScript 5.1+. The dev compiler may be a newer TS version — only the output is downleveled."(发布的 .d.ts 必须能被 TS 5.1+ 消费;开发编译器可以更新,只有输出被降级)。脚本的具体替换规则包括:
| 源类型(较新 TS) | 替换为(TS 5.1 兼容) | 所属版本 |
|---|---|---|
ArrayIterator/MapIterator/SetIterator/StringIterator | IterableIterator | TS 5.6 |
BuiltinIteratorReturn | any | TS 5.6 |
IteratorObject<...> | IterableIterator<T1>(保留第一个类型参数,见GENERIC_REPLACEMENTS) | TS 5.6 |
AsyncIteratorObject<...> | AsyncIterableIterator<T1> | TS 5.6 |
WeakKey | object | TS 5.2 |
Disposable/AsyncDisposable | object | TS 5.2 |
注意IteratorObject这类泛型不能简单用正则替换——其第一个类型参数本身可能包含嵌套尖括号(如IteratorObject<Map<K, V>, undefined>),所以 downlevel-dts.mjs 用括号深度计数(bracket depth counting)来定位第一个类型参数的边界,再折叠多余参数。
另外需要了解:在未来的主版本中提高最低支持的 TypeScript 版本对使用者而言属于破坏性变更(breaking change),任何此类变更都会在该版本的迁移指南中明确说明。因此在升级 Handsontable 主版本前,务必留意迁移指南中的版本要求说明。
类型入口:两个入口点
Handsontable 从两个入口点暴露类型,两者提供完全相同的类型,选择哪个取决于你如何导入库本身:
完整包(Full package)——所有模块已预先注册,不做 tree shaking:
import Handsontable, { GridSettings, HotInstance } from 'handsontable';基础包(Base package)——当你只导入需要的模块时使用:
import Handsontable, { GridSettings, HotInstance } from 'handsontable/base';这些入口的类型映射在 handsontable/package.json 的exports字段中有明确声明:.(根入口)的import条件指向./index.d.mts、require条件指向./index.d.ts;./base入口同理指向./base.d.mts/./base.d.ts。也就是说,无论是 ESM 还是 CommonJS 消费者,都能拿到对应的类型声明。
对于下面列出的所有类型,也都可以使用 TypeScript 的import type语法,它不会产生任何运行时输出:
import type { GridSettings, HotInstance } from 'handsontable';此外,仓库还单独导出了handsontable/settings子路径(见 handsontable/package.json),核心设置类型如RowObject、CellValue、CellChange、ChangeSource、ColumnSettings、CellMeta、CellProperties等都从这里对外再导出,外部消费者可以直接import ... from 'handsontable/settings'(对应源码 handsontable/src/settings.ts 的注释说明)。
类型参考(Type reference)
配置类型(Configuration types)
这些类型描述传入new Handsontable()或updateSettings()的设置对象:
| 类型 | 说明 |
|---|---|
GridSettings | 所有 Handsontable 配置项。用于顶层设置对象。 |
ColumnSettings | 列级覆盖。columns数组中的每一项使用该类型。 |
CellProperties | 全局 → 列 → 单元格级联合并后的单元格级设置。渲染时只读。 |
CellMeta | 可变(mutable)的单元格级元数据,存储在hot.getCellMeta()中。继承CellProperties。 |
Events | 所有 hook 回调签名,以 hook 名为键。用于为单个 hook 函数标注类型。 |
SanitizerContext | 网格传给sanitizer选项的写入面(write surface)。用它标注选项的第二个参数可获得这些值的补全。 |
TextExtractorContext | 网格传给textExtractor选项的消费面。用它标注选项的第二个参数可获得这些值的补全。 |
import type { GridSettings, ColumnSettings } from 'handsontable'; const columns: ColumnSettings[] = [ { data: 'name', type: 'text' }, { data: 'revenue', type: 'numeric', numericFormat: { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 } }, ]; const settings: GridSettings = { data: myData, columns, licenseKey: 'non-commercial-and-evaluation', };从源码结构看,GridSettings定义在 handsontable/src/core/settings.ts,它带有一个宽泛的索引签名([key: string]: any)以便允许任意插件/元数据键。正因如此,handsontable/src/settings.ts 中定义ColumnSettings时使用了Omit<RemoveIndexSignature<GridSettings>, 'data'>:先用RemoveIndexSignature剥掉索引签名,再执行Omit,否则Omit会把所有命名选项塌缩成裸索引签名(所有读取都变成any,自动补全消失)。ColumnSettings重新声明了data?: string | number | ColumnDataGetterSetterFunction,使其语义更贴合"每列一个 data"。
CellMeta在 handsontable/src/settings.ts 中扩展了ColumnSettings,额外加入className、readOnly、valid、comment、isSearchResult、hidden、skipRowOnPaste等单元格级属性;CellProperties(同文件 L112-L119)再叠加渲染期计算出的row、col、instance、visualRow、visualCol、prop字段。
SanitizerContext在 handsontable/src/core/settings.ts 中定义为字符串联合:'header' | 'password' | 'contextMenu' | 'selectEditor' | 'dialog' | 'notification' | 'CopyPaste.paste' | 'CopyPaste.paste.sourceData' | (string & {})。(string & {})这一成员是为了兼容调用方自定义 sanitizer、与其他库共用 sanitizer 或未来新增写入面的场景——代价是类型无法拒绝拼写错误的值。
数据类型(Data types)
这些类型描述进出网格的值:
| 类型 | 说明 |
|---|---|
CellValue | 单个单元格的值:string \| number \| boolean \| null \| undefined。 |
CellChange | 单个变更元组:[row, column, oldValue, newValue]。传给afterChange和beforeChange。 |
RowObject | 以普通对象表示的数据行,当data是对象数组时使用。 |
ChangeSource | 所有内置变更来源标识符的字符串联合(如'edit'、'loadData'、'UndoRedo.undo')。 |
SourceRowData | 任何索引转换之前的原始源数据行。 |
ColumnDataGetterSetterFunction | 列data选项作为 getter/setter 函数的形态。使用函数数据源时,变更 hook 会以它作为prop传入。 |
SelectOptionsObject | select和autocomplete单元格类型的选项条目:{ value, label }。 |
RangeType | 单元格范围描述符:{ from: CellCoords, to: CellCoords }。 |
import type { CellChange, ChangeSource } from 'handsontable'; function onAfterChange(changes: CellChange[] | null, source: ChangeSource): void { if (!changes || source === 'loadData') return; for (const [row, col, , newValue] of changes) { console.log(`Cell [${row}, ${col}] changed to`, newValue); } }源码层面,这些类型的精确定义位于 handsontable/src/settings.ts:
CellValue = unknown(L19):文档说明它"可以是任何值以支持自定义单元格数据类型,但默认是string | number | boolean | undefined";RowObject是{ [prop: string]: unknown }(L12-L14),可携带任意属性,甚至可定义__children数组支持嵌套行;CellChange = [number, string | number | ColumnDataGetterSetterFunction, CellValue, CellValue](L47):元组中的第二项是prop——可能是属性名、列索引,或在columns[].data为函数时是那个 getter/setter 函数本身;ChangeSource的完整联合(L52-L58)包含'auto'、'edit'、'loadData'、'updateData'、'populateFromArray'、'spliceCol'、'spliceRow'、'timeValidate'、'dateValidate'、'validateCells',以及各插件来源如'Autofill.fill'、'ContextMenu.clearColumn'、'CopyPaste.paste'、'CopyPaste.cut'、'UndoRedo.redo'、'UndoRedo.undo'、'ColumnSummary.set'、'DataProvider.revert'等;SourceRowData = RowObject | CellValue[](L31):一行源数据可以是值数组,也可以是键值对象;ColumnDataGetterSetterFunction(L37-L40)是重载函数类型:单参(仅row)用于读取,双参(row, value)用于写入。
实例类型(Instance type)
| 类型 | 说明 |
|---|---|
HotInstance | new Handsontable()返回的 Handsontable 实例。用于标注 ref 和接受活动实例的参数。 |
import Handsontable from 'handsontable'; import type { HotInstance } from 'handsontable'; let hot: HotInstance; hot = new Handsontable(document.querySelector('#grid')!, { data: myData, licenseKey: 'non-commercial-and-evaluation', });HotInstance接口定义在 handsontable/src/core/types.ts,它描述了实例的完整 API 面:包括addHook/removeHook/runHooks等 hook 方法(其中addHook等方法对已知 hook 名会按Events[K]精确推导回调签名,对未知字符串则回退到宽松的HookCallback)、getSettings()/updateSettings()、getSelectedRange()等选区方法,以及更多实例方法。HotInstance与RangeType都从 handsontable/src/index.ts 对外导出。
几何类(Geometry classes)
CellCoords和CellRange是运行时类(非仅类型),同时也可用作类型注解:
| 导出 | 种类 | 说明 |
|---|---|---|
CellCoords | 类 | 一个{ row, col }坐标对。由选择与导航 API 返回。 |
CellRange | 类 | 一个{ from: CellCoords, to: CellCoords }范围。由getSelectedRange()返回。 |
IndexMapper | 类 | 行或列的索引转换器。类型化访问hot.rowIndexMapper和hot.columnIndexMapper。 |
RangeType | 类型 | 一个纯对象范围描述符,用于不需要CellRange实例的 API。 |
import { CellRange } from 'handsontable'; function logSelection(ranges: CellRange[]): void { for (const range of ranges) { console.log(`from [${range.from.row}, ${range.from.col}] to [${range.to.row}, ${range.to.col}]`); } }CellCoords、CellRange从 handsontable/src/base.ts 重新导出(见 handsontable/src/index.ts),IndexMapper来自翻译层./translations(L212)。注意CellRange是值(value),所以上面的import { CellRange }会保留运行时导入,这点与import type的纯类型导入不同。
单元格函数注册表类型(Cell function registry types)
用这些字符串联合类型把type、renderer、editor和validator设置约束为已注册的名称:
| 类型 | 说明 |
|---|---|
CellType | 所有已注册单元格类型别名的联合:'text' \| 'numeric' \| 'checkbox' \| ...。 |
EditorType | 所有已注册编辑器别名的联合。 |
RendererType | 所有已注册渲染器别名的联合。 |
ValidatorType | 所有已注册验证器别名的联合。 |
import type { ColumnSettings, CellType } from 'handsontable'; function buildColumn(type: CellType): ColumnSettings { return { type }; }这些联合类型是由各注册表模块的类型别名typeof推导出来的:CellType定义于 handsontable/src/cellTypes/registry.ts(typeof AUTOCOMPLETE_TYPE | typeof CHECKBOX_TYPE | typeof DATE_TYPE | ...),EditorType于 handsontable/src/editors/index.ts,RendererType于 handsontable/src/renderers/index.ts。因此,当你通过registerCellType()/registerEditor()/registerRenderer()/registerValidator()注册自定义项时,联合类型会随注册表声明同步演进——这是"注册表驱动"类型设计的体现。
编辑器基类类型(Editor base type)
| 类型 | 说明 |
|---|---|
BaseEditorInstance | BaseEditor类的类型。用于在自定义编辑器代码中标注参数和返回值。 |
Hooks 注册表类型(Hooks registry type)
| 类型 | 说明 |
|---|---|
HooksRegistry | 描述静态的Handsontable.hooks对象(如.getRegistered())。在把 hooks 注册表作为类型化参数传递时很有用。 |
import type { HooksRegistry } from 'handsontable'; function listHooks(hooks: HooksRegistry): string[] { return hooks.getRegistered(); }HooksRegistry从 handsontable/src/base.ts 导出(见 handsontable/src/index.ts)。
主题类型(Theme types)
这些类型在使用主题 API(themeName选项)或构建自定义主题时相关:
| 类型 | 说明 |
|---|---|
ThemeConfig | registerTheme()的完整配置对象。 |
ThemeParams | 创建主题实例的构造参数。 |
ThemeBuilder | 主题工厂返回的 builder 对象。 |
ThemeColorScheme | 'light' \| 'dark' \| 'auto'。 |
ThemeColorsConfig | 主题配置内的颜色 token 覆盖。 |
ThemeDensityConfig | 行高与单元格内边距 token 覆盖。 |
ThemeDensitySizes | 密度配置项内的数值尺寸。 |
ThemeIconsConfig | 图标 token 覆盖。 |
ThemeLightDarkValue | 带独立亮/暗变体的 token 值。 |
ThemeSizingConfig | 字体与间距 token 覆盖。 |
ThemeTokenValue | 单个设计 token 值。 |
ThemeTokensConfig | ThemeConfig内部使用的完整 token 映射。 |
BaseTheme | 所有内置主题继承的基础主题对象。 |
仓库中主题系统的实现位于 handsontable/src/themes 目录,内置主题的 CSS 产物通过build:themes-css/build:themes-umd等脚本构建(见 handsontable/package.json)。
Overlay 类型(Overlay type)
| 类型 | 说明 |
|---|---|
OverlayType | 所有 Walkontable overlay 标识符的字符串联合:'top' \| 'bottom' \| 'left' \| 'right' \| ...。在高级渲染定制中很有用。 |
OverlayType的精确定义在 handsontable/src/3rdparty/walkontable/src/types.ts:'inline_start' | 'top' | 'top_inline_start_corner' | 'bottom' | 'bottom_inline_start_corner' | 'master'。可以看到它采用逻辑/物理方向混合命名(inline_start、master),实际取值以源码为准。
公共 API 的隐式类型化(Implicit typing)
上面列出的类型是你在需要显式导入时才用的——即当你需要为自己的变量、参数或返回值标注类型时。
公共 API 的其余部分——插件、编辑器、渲染器、验证器、单元格类型,以及Handsontable类本身——都自带 TypeScript 声明。导入值的同时就会自动获得完整的类型安全和 IDE 自动补全,无需额外导入类型:
// 无需类型导入 -- ContextMenu 已完全类型化。 import { ContextMenu } from 'handsontable/plugins'; // 无需类型导入 -- AutocompleteEditor 已完全类型化。 import { AutocompleteEditor } from 'handsontable/editors'; // 无需类型导入 -- numericRenderer 已完全类型化。 import { numericRenderer } from 'handsontable/renderers'; // 无需类型导入 -- Handsontable 类及其实例方法已完全类型化。 import Handsontable from 'handsontable'; const hot = new Handsontable(container, { licenseKey: 'non-commercial-and-evaluation' }); hot.loadData(myData); // IDE 自动补全和类型检查在这里生效。上文类型参考中的显式import type { ... }模式,只在"值不在当前作用域内、需要把类型单独用作注解"时才必要。仓库的exports映射也为子路径提供了声明:./plugins/*/index.*、./editors/*/index.*、./renderers/*/index.*、./cellTypes/*/index.*等都会随构建产物一起发布(见 handsontable/package.json)。
值得补充的是,这些声明的正确性由专门的类型测试(type tests)守护:仓库在handsontable/src下有 69 个*.types.ts文件(如 handsontable/src/tests/public-types.types.ts),配合test:types脚本(handsontable/package.json)在 CI 中编译验证——它们不产生运行时行为,而是确保每个公开类型在真实使用场景下能通过类型检查。例如 handsontable/src/core/settings.ts 中提到的_strippedWidthTyped、_columnNamedOptionsTyped、_hotColumnGetValueFn等类型测试,就专门守护RemoveIndexSignature与ColumnSettings的类型行为。
常见模式(Common patterns)
类型化设置对象(Type the settings object)
直接给 settings 标注类型,让 TypeScript 校验每一个选项:
import type { GridSettings } from 'handsontable'; const settings: GridSettings = { data: employees, colHeaders: ['Name', 'Department', 'Hire date'], columns: [ { data: 'name' }, { data: 'department' }, { data: 'hireDate', type: 'date', dateFormat: { year: 'numeric', month: '2-digit', day: '2-digit' } }, ], licenseKey: 'non-commercial-and-evaluation', };使用Events类型化 hook 回调
Events把每个 hook 名映射到其回调签名。用Events[hookName]提取特定回调类型:
import type { Events, CellChange, ChangeSource } from 'handsontable'; const onAfterChange: Events['afterChange'] = (changes: CellChange[] | null, source: ChangeSource) => { if (!changes) return; console.log(source, changes); }; hot.addHook('afterChange', onAfterChange);得益于HotInstance.addHook的重载设计(见 handsontable/src/core/types.ts),当 hook 名是已知的keyof Events时,回调参数会被精确推导;即使不手动标注,addHook('afterChange', cb)也能获得参数补全。
类型化自定义渲染器(Type a custom renderer)
自定义渲染器接收类型化的CellProperties参数:
import Handsontable from 'handsontable'; import type { CellProperties, HotInstance, GridSettings } from 'handsontable'; function statusRenderer( hotInstance: HotInstance, TD: HTMLTableCellElement, row: number, col: number, prop: string | number, value: string, cellProperties: CellProperties ): void { Handsontable.renderers.TextRenderer(hotInstance, TD, row, col, prop, value, cellProperties); TD.style.color = value === 'Active' ? 'green' : 'red'; }注意CellProperties比CellMeta多出渲染期字段(row、col、instance、visualRow、visualCol、prop,见 handsontable/src/settings.ts),这正是渲染器签名里第七个参数的类型。更完整的自定义渲染器写法可参考单元格渲染器指南。
类型化自定义编辑器(Type a custom editor)
继承BaseEditorInstance(实际使用中常直接继承某个内置编辑器类),获得完整的 IDE 支持:
import { TextEditor } from 'handsontable/editors'; import type { GridSettings } from 'handsontable'; class RatingEditor extends TextEditor { override getValue(): string { return this.TEXTAREA?.value ?? ''; } override setValue(newValue: string): void { if (this.TEXTAREA) { this.TEXTAREA.value = newValue; } } } const settings: GridSettings = { columns: [{ editor: RatingEditor as unknown as string }], licenseKey: 'non-commercial-and-evaluation', };TextEditor等编辑器类从handsontable/editors导入,自带完整类型(见 handsontable/src/editors/index.ts 处的导出)。关于自定义编辑器的完整流程,可参考单元格编辑器指南。
在 React ref 中类型化 HOT 实例
HotTable暴露的 ref 类型是HotTableRef,而非HotInstance——需要通过hotRef.current?.hotInstance读取 Handsontable 实例。wrapper 把网格选项作为独立 props 接收,所以没有settingsprop——把可复用的设置对象类型化为HotTableProps,再展开到<HotTable>上。更多实例 ref 的用法见 React methods。
import { useRef } from 'react'; import { HotTable } from '@handsontable/react-wrapper'; import type { HotTableRef, HotTableProps } from '@handsontable/react-wrapper'; export function Grid() { const hotRef = useRef<HotTableRef>(null); const settings: HotTableProps = { data: myData, licenseKey: 'non-commercial-and-evaluation', }; const selectFirstCell = () => { hotRef.current?.hotInstance?.selectCell(0, 0); }; return ( <HotTable ref={hotRef} {...settings} /> ); }在 Angular 中类型化 HOT 实例
import { Component, ViewChild } from '@angular/core'; import { HotTableComponent, HotTableModule, GridSettings } from '@handsontable/angular-wrapper'; @Component({ standalone: true, imports: [HotTableModule], template: `<hot-table [settings]="gridSettings" />`, }) export class GridComponent { @ViewChild(HotTableComponent) hotTable!: HotTableComponent; readonly gridSettings: GridSettings = { data: myData, licenseKey: 'non-commercial-and-evaluation', }; }通过hotTable.hotInstance读取 Handsontable 实例。
在 Vue ref 中类型化 HOT 实例
<script setup lang="ts"> import { ref } from 'vue'; import { HotTable } from '@handsontable/vue3'; import type { GridSettings } from 'handsontable'; const hotRef = ref<InstanceType<typeof HotTable> | null>(null); const settings: GridSettings = { data: myData, licenseKey: 'non-commercial-and-evaluation', }; </script> <template> <HotTable ref="hotRef" :settings="settings" /> </template>ref 被类型化为HotTable组件类型;通过hotRef.value?.hotInstance读取HotInstance。
小结与延伸阅读
Handsontable 的类型体系可以概括为三层:入口层(handsontable/handsontable/base两个入口提供相同类型)、显式类型层(本文类型参考中列出的配置、数据、实例、几何、注册表、主题、Overlay 类型,需要时才import type)、隐式类型层(插件、编辑器、渲染器等值导入即带类型)。声明文件由最新 TypeScript 编译后降级到 TS 5.1 兼容,并由 69 个*.types.ts类型测试持续守护。
继续深入可参考:
- 模块化构建(Modules) —— 了解
handsontable/base入口对应的按需注册模块机制 - 自定义插件(Custom plugins)
- 单元格编辑器(Cell editor)
- 单元格渲染器(Cell renderer)
- 类型声明的核心源码:handsontable/src/settings.ts、handsontable/src/core/settings.ts、handsontable/src/core/types.ts、handsontable/src/index.ts
- 声明降级机制:handsontable/scripts/downlevel-dts.mjs
- 类型测试示例:handsontable/src/tests/public-types.types.ts
- 前端
- UI组件
【免费下载链接】handsontable
JavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡
相关推荐
Apollo-CustomApiCredentials的未来路线图:即将到来的功能和改进
Apollo CustomApiCredentials的未来路线图:即将到来的功能和改进 Apollo CustomApiCredentials是一个让您在Ap
Fastify 与 TypeScript 完全指南:类型系统、泛型路由与插件类型安全实战
Fastify 与 TypeScript 完全指南:类型系统、泛型路由与插件类型安全实战 Fastify 内核使用纯 JavaScript 编写,其类型系统在
后端Web框架WebdriverIO TypeScript 配置完全指南:tsx 编译、类型安全与框架类型声明
WebdriverIO TypeScript 配置完全指南:tsx 编译、类型安全与框架类型声明 WebdriverIO 作为面向 Node.js 的下一代浏览
测试质量保障
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考