實戰(zhàn)指南:基于 EditorComponent 的自定義編輯器與渲染器)
Handsontable React 星級評分單元格Star Rating Cell實戰(zhàn)指南基于 EditorComponent 的自定義編輯器與渲染器【免費(fèi)下載鏈接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ?項目地址: https://gitcode.com/gh_mirrors/ha/handsontable本篇實戰(zhàn)指南以 Handsontable 官方 React 封裝handsontable/react-wrapper為基礎(chǔ)手把手帶你構(gòu)建一個可在數(shù)據(jù)網(wǎng)格中直接點擊選分的星級評分單元格編輯態(tài)顯示可交互的星形選擇器支持懸停預(yù)覽、點擊確認(rèn)非編輯態(tài)通過自定義 React 渲染器顯示只讀星形數(shù)值以 1-5 數(shù)字形式存入單元格并可選配范圍校驗器。讀完本文你將掌握 Handsontable 自定義單元格類型的完整鏈路——EditorComponentrender prop 用法、React 渲染器、validator 校驗、CSS 變量主題適配以及按列定制星級數(shù)量等進(jìn)階能力。概覽Overview本指南演示如何用 React 的EditorComponent構(gòu)建一個星級評分編輯器單元格適用于商品評價、反饋表單等需要用戶選擇 1-5 星數(shù)值評級的場景。難度入門Beginner耗時約 15 分鐘依賴庫無需任何外部評分組件庫星級 UI 由本地 React 組件實現(xiàn)你將構(gòu)建什么What Youll Build一個具備以下能力的單元格編輯時顯示可交互的星級評分器非編輯態(tài)通過自定義 React 渲染器展示星形支持選擇前的懸停預(yù)覽hover preview以數(shù)字1-5形式存儲評分值校驗評分范圍例如 0-100提供點擊即選click-to-select交互完全契合 React 組件化架構(gòu)前置條件Prerequisitesnpm install handsontable handsontable/react-wrapper你需要具備React 16.8支持 Hookshandsontable/react-wrapper包基礎(chǔ) React 知識Hooks、JSX第 1 步導(dǎo)入依賴Import Dependenciesimport { HotTable, HotColumn, EditorComponent } from handsontable/react-wrapper; import { registerAllModules } from handsontable/registry; registerAllModules();導(dǎo)入內(nèi)容說明EditorComponent—— 用于創(chuàng)建自定義編輯器的 React 組件HotTable/HotColumn—— React 封裝后的表格與列組件registerAllModules()—— 注冊 Handsontable 全部模塊使用封裝器時必需從倉庫源碼看這三個組件均從handsontable/react-wrapper的入口統(tǒng)一導(dǎo)出見 wrappers/react-wrapper/src/index.tsx而EditorComponent的實現(xiàn)位于 wrappers/react-wrapper/src/hotEditor.tsx。調(diào)用registerAllModules()是為了把內(nèi)置編輯器、渲染器、插件一次性注冊進(jìn) registry省去逐個注冊的樣板代碼。第 2 步創(chuàng)建星級評分組件Star Rating Component創(chuàng)建一個本地組件渲染五顆星并向外部上報懸停與點擊事件interface StarRatingProps { name: string; value: number; editing?: boolean; onStarHover?: (value: number) void; onStarClick?: (value: number) void; } function StarRating({ name, value, editing true, onStarHover, onStarClick }: StarRatingProps) { return ( div classNamestar-rating aria-label{Rating: ${value} out of 5} {[1, 2, 3, 4, 5].map((star) ( span key{${name}-${star}} className{star value ? star filled : star} onMouseEnter{editing ? () onStarHover?.(star) : undefined} onClick{editing ? () onStarClick?.(star) : undefined} ★ /span ))} /div ); }要點分析nameprop 為每顆星生成穩(wěn)定的 React key例如rating-3避免復(fù)用組件時 key 沖突editingprop 在非編輯態(tài)下關(guān)閉懸停與點擊事件處理直接傳undefinedonStarHover在用戶提交前預(yù)覽目標(biāo)分值onStarClick提交選中的分值第 3 步創(chuàng)建編輯器組件Editor Component使用EditorComponent的 render prop 模式封裝編輯器 UIexport const RatingEditor () { return ( EditorComponentnumber {({ value, setValue, finishEditing }) ( div classNamerating-editor StarRating namerating value{Number(value) || 0} onStarHover{(nextValue) setValue(nextValue)} onStarClick{(nextValue) { setValue(nextValue); finishEditing(); }} / /div )} /EditorComponent ); };要點分析EditorComponent包裹編輯器 UIchildren是一個接收編輯器狀態(tài)并返回 JSX 的函數(shù)render propvalue—— 當(dāng)前單元格值數(shù)字評分setValue—— 更新值的函數(shù)finishEditing—— 保存并關(guān)閉編輯器的函數(shù)StarRating—— 渲染本地星形選擇器onStarHover—— 用戶懸停時更新預(yù)覽onStarClick—— 保存所選評分并關(guān)閉編輯器rating-editordiv 位于 render prop 內(nèi)部因此樣式可作用于可見的編輯器區(qū)域關(guān)鍵概念Render prop 模式EditorComponent以函數(shù)作為 children由封裝器注入編輯器狀態(tài)懸停預(yù)覽onStarHover讓用戶在提交前預(yù)覽分值點擊確認(rèn)onStarClick保存并關(guān)閉編輯器源碼級原理EditorComponent內(nèi)部通過useHotEditorT()hook見 wrappers/react-wrapper/src/hotEditor.tsx向 render prop 注入{ value, setValue, finishEditing, isOpen, row, col, mainElementRef }。其中setValue在更新 React 狀態(tài)的同時會把值同步寫回 Handsontable 原生編輯器實例hotCustomEditorInstanceRef.current?.setValue(newValue)而finishEditing()則調(diào)用原生實例的finishEditing()從而觸發(fā) Handsontable 的afterChange等后續(xù)數(shù)據(jù)流。整個橋接鏈路由makeEditorClasswrappers/react-wrapper/src/hotEditor.tsx生成的一個繼承自Handsontable.editors.BaseEditor的CustomEditor類完成React 組件因此可以無縫替換 Handsontable 原生編輯器。EditorComponent還支持onPrepare、onOpen、onClose、onFocus等生命周期回調(diào)以及shortcuts快捷鍵配置下文增強(qiáng)部分會用到onPrepare。第 4 步添加自定義渲染器View Mode Renderer使用 React 組件作為單元格渲染器使非編輯態(tài)也顯示星形const RatingCellRenderer ({ value }: { value: unknown }) ( div classNamerating-cell StarRating namerating-cell value{Number(value) || 0} editing{false} / /div );要點分析渲染器接收value并用StarRating展示editing{false}使非編輯態(tài)的星形不可交互使用獨立的name例如rating-cell保證渲染器內(nèi) React key 穩(wěn)定注意渲染器與編輯器共用同一個StarRating組件這正是本方案“一套 UI、兩種形態(tài)”的核心優(yōu)勢——編輯態(tài)與只讀態(tài)的視覺完全一致。第 5 步添加校驗器Validator可選校驗評分是否落在允許范圍內(nèi)例如 0-100const ratingValidator (value: string | number, callback: (valid: boolean) void) { const parsed parseInt(String(value)); callback(parsed 0 parsed 100); };如果采用嚴(yán)格的 1-5 星量表改用parsed 1 parsed 5即可。說明Handsontable 的校驗器是異步回調(diào)風(fēng)格——無論值來自編輯器還是粘貼等操作都會經(jīng)過校驗器通過callback(valid)報告結(jié)果。校驗失敗時單元格會進(jìn)入無效態(tài)invalid并在事件流中觸發(fā)afterValidate便于進(jìn)一步給出提示。第 6 步添加樣式Styling讓星形評分在單元格與編輯器中都貼合網(wǎng)格主題.star-rating { display: inline-flex; gap: 1px; } .star-rating .star { font-size: 18px; color: #d3d3d3; line-height: 1; cursor: default; user-select: none; transition: color 0.1s; } .rating-editor .star-rating .star { cursor: pointer; } .star-rating .star.filled { color: #ffb400; } .rating-cell { display: flex; align-items: center; margin: 3px 0 0 -1px; } .rating-editor { display: flex; align-items: center; height: 100%; box-sizing: border-box !important; border: none; border-radius: 0; box-shadow: inset 0 0 0 var(--ht-cell-editor-border-width, 2px) var(--ht-cell-editor-border-color, #1a42e8), 0 0 var(--ht-cell-editor-shadow-blur-radius, 0) 0 var(--ht-cell-editor-shadow-color, transparent); background-color: var(--ht-cell-editor-background-color, #ffffff); padding: var(--ht-cell-vertical-padding, 4px) var(--ht-cell-horizontal-padding, 8px); font-family: var(--ht-font-family, inherit); font-size: var(--ht-font-size, 14px); line-height: var(--ht-line-height, 1.5); }要點分析.star-rating負(fù)責(zé)本地星形組件的橫向布局.star-rating .star.filled為已選中星形著色.rating-cell在非編輯態(tài)下將星形與單元格對齊.rating-editor使用 Handsontable 的 CSS 變量--ht-cell-editor-border-width、--ht-cell-editor-background-color、--ht-cell-vertical-padding等均帶默認(rèn)值兜底來適配焦點邊框、背景與內(nèi)邊距使編輯器外觀與網(wǎng)格主題一致切換深色/淺色主題時無需改動樣式第 7 步準(zhǔn)備示例數(shù)據(jù)Sample Data使用帶rating屬性的數(shù)據(jù)可按需添加其他列。以商品表為例export const data [ { product: Dashboard Pro, category: Analytics, rating: 5, reviews: 342, price: 49 }, { product: Form Builder, category: Tools, rating: 4, reviews: 218, price: 29 }, { product: Chart Engine, category: Analytics, rating: 3, reviews: 156, price: 39 }, { product: Auth Module, category: Security, rating: 5, reviews: 89, price: 19 }, { product: File Manager, category: Storage, rating: 2, reviews: 64, price: 15 }, { product: Email Service, category: Communication, rating: 4, reviews: 275, price: 25 }, { product: Search Index, category: Tools, rating: 1, reviews: 31, price: 35 }, { product: Cache Layer, category: Infra, rating: 4, reviews: 112, price: 20 }, ];要點分析每行包含product、category、rating、reviews、price字段rating列使用星級編輯器與渲染器其他列可繼續(xù)使用文本或數(shù)字類型第 8 步接入 HandsontableUse in Handsontable將編輯器、渲染器、校驗器綁定到 rating 列const ExampleComponent () { return ( HotTable data{data} colHeaders{[Product, Category, Rating, Reviews, Price]} autoRowSize{true} rowHeaders{true} heightauto width100% autoWrapRow{true} headerClassNamehtLeft licenseKeynon-commercial-and-evaluation HotColumn dataproduct typetext width{240} / HotColumn datacategory typetext width{120} / HotColumn datarating width{150} editor{RatingEditor} renderer{RatingCellRenderer} validator{ratingValidator} / HotColumn datareviews typenumeric width{80} / HotColumn dataprice typenumeric width{80} / /HotTable ); };要點分析editor{RatingEditor}—— 單元格激活時啟用星級評分編輯器renderer{RatingCellRenderer}—— 非編輯態(tài)顯示星形validator{ratingValidator}—— 確保評分在允許范圍如 0-100內(nèi)datarating—— 綁定每行數(shù)據(jù)中的rating屬性關(guān)鍵特性編輯態(tài)與非編輯態(tài)都顯示星形評分以數(shù)字1-5存儲支持校驗且全程 TypeScript 類型安全完整可運(yùn)行示例見倉庫中的 example1.tsxTypeScript 版、example1.jsxJavaScript 版及配套樣式 example1.css。在 JSX 版中EditorComponent不帶泛型參數(shù)即可正常使用。工作原理完整流程How It Works - Complete Flow初始渲染RatingCellRenderer按當(dāng)前評分顯示星形例如 3 顆實心星用戶雙擊或按 Enter編輯器打開編輯器打開EditorComponent在單元格位置顯示星形選擇器星級展示StarRating顯示當(dāng)前值空星表示剩余刻度用戶交互懸停星形 →onStarHover通過setValue更新預(yù)覽點擊星形 →onStarClick保存值并調(diào)用finishEditing()校驗ratingValidator執(zhí)行例如檢查值是否在 0-100 之間保存數(shù)字值寫入單元格編輯器關(guān)閉RatingCellRenderer在非編輯態(tài)顯示更新后的星形結(jié)合源碼看第 3 步的“位置對齊”由EditorComponent內(nèi)部的applyEditorPositionwrappers/react-wrapper/src/hotEditor.tsx完成——它讀取被編輯單元格的getBoundingClientRect()把編輯器絕對定位到單元格上方并掛載afterScrollHorizontally/afterScrollVertically鉤子滾動時自動跟隨刷新位置單元格不可用時會自動關(guān)閉編輯器。增強(qiáng)功能Enhancements1. 自定義星級數(shù)量改為 10 分制等任意刻度interface StarRatingProps { name: string; value: number; starCount?: number; editing?: boolean; onStarHover?: (value: number) void; onStarClick?: (value: number) void; } function StarRating({ name, value, starCount 5, editing true, onStarHover, onStarClick }: StarRatingProps) { const stars Array.from({ length: starCount }, (_, index) index 1); return ( div classNamestar-rating aria-label{Rating: ${value} out of ${starCount}} {stars.map((star) ( span key{${name}-${star}} className{star value ? star filled : star} onMouseEnter{editing ? () onStarHover?.(star) : undefined} onClick{editing ? () onStarClick?.(star) : undefined} ★ /span ))} /div ); }2. 自定義星形顏色按需調(diào)整配色.star-rating .star { color: #e0e0e0; } .star-rating .star.filled { color: #ffd700; }3. 替代方案基于 HTML 的渲染器主示例在非編輯態(tài)使用 React 組件RatingCellRenderer。如果你更傾向于非 React 渲染器可使用rendererFactoryimport { rendererFactory } from handsontable/renderers; const starRenderer rendererFactory(({ td, value }) { const rating Number(value) || 0; const stars ★.repeat(rating) ☆.repeat(5 - rating); td.innerHTML div style font-size: 1.2em; color: #ffb400; letter-spacing: 2px; ${stars}/div ; }); // Use in HotColumn HotColumn datarating width{150} editor{RatingEditor} renderer{starRenderer} /源碼佐證rendererFactory定義于 handsontable/src/renderers/factory.ts它把標(biāo)準(zhǔn)渲染器函數(shù)簽名instance, td, row, column, prop, value, cellProperties包裝成更易用的對象參數(shù)回調(diào)倉庫測試 handsontable/src/renderers/tests/index.spec.js 也驗證了用該工廠創(chuàng)建自定義渲染器的用法。注意渲染器需直接操作tdDOM本例用innerHTML因此返回的仍是 Handsontable 原生渲染器而非 React 組件。4. 從單元格屬性讀取配置進(jìn)階借助onPrepare實現(xiàn)按列定制星級數(shù)量const RatingEditor () { const [starCount, setStarCount] useState(5); const onPrepare (_row, _column, _prop, _TD, _originalValue, cellProperties) { if (cellProperties.starCount ! null) { setStarCount(cellProperties.starCount); } }; return ( div classNamerating-editor EditorComponentnumber onPrepare{onPrepare} {({ value, setValue, finishEditing }) ( StarRating namerating starCount{starCount} value{Number(value) || 0} onStarHover{(nextValue) setValue(nextValue)} onStarClick{(nextValue) { setValue(nextValue); finishEditing(); }} / )} /EditorComponent /div ); }; // Use with different star counts per column HotColumn editor{RatingEditor} starCount{5} datarating titleRating (1-5) / HotColumn editor{RatingEditor} starCount{10} datascore titleScore (1-10) /原理說明onPrepare是 Handsontable 編輯器生命周期中“編輯器實例準(zhǔn)備就緒”的鉤子。在 wrappers/react-wrapper/src/hotEditor.tsx 中EditorComponent把onPrepare透傳給底層編輯器實例其第六個參數(shù)cellProperties即當(dāng)前單元格的完整配置對象——因此可以通過HotColumn上的自定義屬性如starCount按列注入不同配置。5. 處理空值確保組件能正確處理undefined或nullvalue{Number(value) || 0}當(dāng)單元格無值時顯示空星。無障礙Accessibility本地StarRating組件在評分容器上設(shè)置了aria-label。如需鍵盤支持可將每顆星渲染為按鈕button typebutton className{star value ? star filled : star} onMouseEnter{editing ? () onStarHover?.(star) : undefined} onClick{editing ? () onStarClick?.(star) : undefined} aria-label{Set rating to ${star}} ★ /button鍵盤導(dǎo)航建議Tab聚焦到編輯器方向鍵添加按鍵處理器后可在星形間導(dǎo)航Enter/Space選擇星形Escape取消編輯性能考量Performance Considerations為什么這套方案是快的無外部評分包星級 UI 只依賴一個輕量本地組件React 虛擬 DOM僅當(dāng)值變化時才發(fā)生更新聚焦的回調(diào)onStarHover與onStarClick各司其職職責(zé)單一無多余重渲染編輯器關(guān)閉即卸載從實現(xiàn)層面看EditorComponent的根 div 在關(guān)閉時被設(shè)置為display: nonewrappers/react-wrapper/src/hotEditor.tsx編輯器只在需要時可見避免了對網(wǎng)格渲染管線造成持續(xù)負(fù)擔(dān)懸停預(yù)覽僅走 React 內(nèi)部的setValue狀態(tài)更新并不會觸發(fā) Handsontable 的單元格重渲染。TypeScript 支持TypeScript SupportEditorComponent是完整類型化的。為數(shù)字評分指定值類型EditorComponentnumber {({ value, setValue, finishEditing }) { // TypeScript knows value is number | undefined // TypeScript knows setValue accepts number return ( StarRating namerating value{Number(value) || 0} onStarHover{(nextValue) setValue(nextValue)} onStarClick{(nextValue) { setValue(nextValue); finishEditing(); }} / ); }} /EditorComponent借助泛型numbervalue被推斷為number | undefined、setValue只接受number從而在編譯期杜絕把字符串或錯誤類型寫入評分單元格。最佳實踐Best Practices將值強(qiáng)轉(zhuǎn)為數(shù)字—— 單元格值可能是字符串統(tǒng)一用Number(value) || 0處理提供nameprop—— 編輯器與渲染器使用不同名稱如rating與rating-cell保證 React key 穩(wěn)定點擊時調(diào)用finishEditing()—— 星形點擊即確認(rèn)選擇并關(guān)閉編輯器用onStarHover做預(yù)覽—— 提交前展示選擇結(jié)果改善交互體驗使用自定義渲染器——RatingCellRenderer配合editing{false}在非編輯態(tài)顯示星形保持 UI 一致添加校驗器—— 用ratingValidator限定值域如 0-100 或 1-5并即時反饋你學(xué)到了什么What you learned你創(chuàng)建了一個本地StarRating組件并將其集成為 Handsontable 在 React 中的單元格編輯器用EditorComponent的 render prop 模式管理懸停預(yù)覽與點擊確認(rèn)用 React 組件渲染器在非編輯態(tài)顯示星形。整個過程不依賴任何外部評分庫數(shù)值以數(shù)字形式存儲并可選配校驗器保障數(shù)據(jù)合法性。下一步Next stepsStar Rating (JavaScript) —— 同一思路的 JavaScript 版本使用editorFactory與 SVG 星形同樣零外部依賴Star Rating Editor (Angular) —— 基于HotCellEditorAdvancedComponent的 Angular 版本Colorful Picker (React) —— 另一個 ReactEditorComponent示例演示顏色選擇器【免費(fèi)下載鏈接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ?項目地址: https://gitcode.com/gh_mirrors/ha/handsontable創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考