
Slate v2 泛型类型系统重构以 Plate 为基准替代 CustomTypes 声明合并【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本文是 plate 仓库中 Slate v2 类型系统专项计划的深度解读它回答了一个核心问题——当旧版 Slate 依赖CustomTypes/ExtendedType声明合并declaration merging来扩展类型时Slate v2 为什么要彻底放弃这条路线转而采用与 Plate 对齐的Value 优先泛型模型Value TElement[]EditorV extends Value。读完本文你将掌握 Slate v2 的目标类型契约、Value/TElement/TText及其派生工具ElementOf、TextOf、NodeOf、EditorMarksOf等的语义与使用方式理解核心节点泛型 → 操作泛型 → 历史/React 泛型的分批落地顺序以及配套的硬性质量门禁与红线清单文中还结合仓库packages/slate的既有实现给出源码级佐证。1. 背景与动机为什么放弃 CustomTypes 声明合并旧版 Slate 生态中应用侧扩展节点类型的主流方式是通过declare module slate做模块增强module augmentation让CustomTypes接口被全局合并进而驱动Element、Text、Editor等内置类型的细化。这个机制的问题在 Slate v2 的实践中被放大全局类型污染应用的 custom types 一旦合并进slate模块包内部的所有代码都会看到应用专属类型导致源码优先source-first的站点类型检查必须依赖构建产物或专门的 workaround 才能通过。双份真相来源只要CustomTypes仍作为临时机制保留核心泛型落地后就会出现两套互相矛盾的文档与心智模型。无法表达派生关系声明合并是拍平的节点、文本、元素之间的派生如这个编辑器的文本联合类型是什么无法通过TextOfE这类计算型工具获得。该计划的 Verdict 一锤定音DropCustomTypes/ExtendedTypeas the primary type system。理由是 Plate 已经用更好的 DX 形态解决了这个问题——文档形状由Value携带编辑器 API 从该 Value 派生节点/文本/元素类型插件与编辑器层再在之上叠加自己的泛型。Slate v2 应当拉取pull这一模型而不是另起炉灶推断一套平行模型。计划的 Non-Goals 也划清了边界不在本计划内重写浏览器编辑内核、不重新设计公开编辑方法、不保留声明合并控制包内部的混合状态、不把 schema 对象作为规范类型模型、不在类型边界就绪前修复 source-first 站点别名。2. 目标类型模型Value 优先计划给出的核心契约只有三行type Value TElement[]; type EditorV extends Value Value EditorBaseV RuntimeEditorV;其余一切类型都从Value、TElement、TText与EditorV派生。这意味着文档的形状由Value单一来源承载编辑器实例类型携带文档类型参数V所有查询、变换、操作、历史、React 渲染的类型都通过ValueOfE、ElementOfE、TextOfE、NodeOfE等计算型工具回溯到V。对照仓库中 Plate 的packages/slate实现这一模型已经以完整形态存在可作为 Slate v2 移植的参照在 editor-type.ts 中EditorV extends Value Value由EditorBaseV与api/tf/transforms组合而成Value TElement[]ValueOfE extends Editor E[children]编辑器实例的operations字段被定义为OperationDescendantInV[]marks为EditorMarks | nullselection为EditorSelection TRange | null在 create-editor.ts 中工厂函数签名就是createEditor V extends Value({ children, selection }: { children?: V; selection?: Editor[selection] } {})并在末尾以return editor as unknown as EditorV完成类型边界的收敛——这正是计划要求的内部构造 base editor仅在边界处 castcreate-editor.spec.ts 验证了createEditor()会同步getMarks/insertText/select/point等代表性方法到editor.api与editor.tf且withHistory(createEditor())之后history、undo、redo、apply均可用。从源码结构看这套实现把运行时可变存储留在内部把泛型边界留在公开的EditorV与createEditorV()上与计划的内部实例参数保持结构化宽松、公开/静态契约作为泛型边界策略完全一致。3. Plate 对齐矩阵Source of Truth计划以一张矩阵锁定了 Slate v2 各类型文件的移植源头核心规则是能直接照搬 Plate 的地方照搬只允许在明确列出的点上做超越。下表整理自原文档并保留了关键落点Plate 源文件仓库内Plate 泛型定律Slate v2 目标落点允许漂移editor-type.tsEditorV、EditorBaseV、Value、ValueOfE、EditorSelectioninterfaces/editor.ts、create-editor.ts、core/public-state.ts仅 marks 改进element.tsTElement、Element TElement、ElementInV、ElementOfN、ElementOrTextOfEinterfaces/element.ts、interfaces/node.ts无text.tsTText、Text TText、TextInV、TextOfNinterfaces/text.ts、interfaces/editor.ts有marks 改进node.tsTNode、Ancestor、Descendant、NodeOfN、AncestorOfN、DescendantOfN、NodePropsNinterfaces/node.ts无node-entry.tsNodeEntryOfE、ElementEntryOfE、TextEntryOfE、AncestorEntryOfE、DescendantEntryOfEinterfaces/node-entry.ts或interfaces/node.ts无editor-api.ts每个查询/match 选项在节点形状相关处泛型于V extends Valueinterfaces/editor.ts、editor/*.ts无editor-transforms.tsEditorTransformsV变换选项与节点类型由ValueOfE派生interfaces/transforms/*.ts、transforms-*/*.ts无legacy-editor.ts旧变换形态的兼容桥不作为主 API 移植硬切公开兼容packages/core/src/lib/editor/SlateEditor.tsTSlateEditorV, P在 Slate 泛型之上叠加插件配置泛型core/editor-extension.ts、core/extension-registry.ts精神一致命名可适配packages/core/src/react/editor/PlateEditor.tsTPlateEditorV, P扩展 Slate 编辑器加入 React/插件运行时slate-react/src/plugin/react-editor.ts、context.tsx架构一致、实现不同矩阵传递了两个关键信息第一移植对象是模型model而非插件运行时——SlateEditor.ts/PlateEditor.ts只借鉴分层泛型的思想不照搬 Plate 的插件实现第二允许的漂移被显式列出marks、legacy 兼容桥不允许顺手发明新的平行模型。4. 超越 Plate 的三项决策计划只允许在三处比 Plate 做得更好且都给出了理由与代码形态。4.1 Marks从Recordstring, any到文本联合派生Plate 现状type EditorMarks Recordstring, any;Slate v2 目标type MarksOfTextT extends TText PartialOmitT, text; type EditorMarksOfE extends Editor MarksOfTextTextOfE; type EditorMarksV extends Value Value MarksOfTextTextInV;理由mark 的键应该来自编辑器文本联合类型而不是any。这样既保留自定义 mark 的灵活性又不丢失 Agent / IDE 的类型引导。在仓库 text.ts 中可以看到派生工具已经就位MarkKeysOfN extends TNode、MarksInV extends Value MarksOfV[number]、MarksOfN基于UnionToIntersection与NodePropsTextOfN计算而 editor-type.ts 中EditorMarks Recordstring, any仍是文档所指的待改进弱类型——这正是 Slate v2 计划要替换的那一行。4.2 Operations 与 Commits以V为单位线程化Plate 的操作载荷大多停留在静态Node/Operation形状。Slate v2 的立场是操作是协作真相collaboration truth提交是本地运行时真相runtime truth如果操作载荷塌缩为无类型的Node那么历史、Yjs、React 脏检查与测试恰好会在最需要精度的地方丢失类型信息。因此目标为type OperationV extends Value Value ... type EditorTransactionV extends Value Value ... type EditorSnapshotV extends Value Value ... type EditorCommitV extends Value Value ...并给出了分阶段落地的顺序不得永久跳过先核心节点/编辑器/查询泛型再操作子类型泛型最后 commit/历史/React 泛型。仓库中 operation.ts 已把Operation泛型推进到节点层OperationN extends Descendant Descendant其子类型InsertNodeOperationN、SetNodeOperationN1, N2、SplitNodeOperationN、MergeNodeOperationN、RemoveNodeOperationN均以PartialNodePropsN携带节点属性载荷而 history.ts 的History/Batch仍以operations: Operation[]存储——这两处正是文档规划继续把泛型从节点层提升到 Value 层的接缝。4.3 Schema 别名只作为写作助手不作为规范模型不把Value替换成 schema 对象但为插件作者增加一个仅用于声明式书写的别名type EditorSchema TElementUnion extends TElement TElement, TTextUnion extends TText TText, { Element: TElementUnion; Text: TTextUnion; }; type ValueOfSchemaS extends EditorSchema S[Element][];理由插件作者习惯按元素/文本联合来思考而 Plate 兼容的核心仍以Value为思考单位ValueOfSchema在两者之间架一座桥但核心类型模型不变。5. 规范泛型词汇表计划把 Slate v2 的全部核心类型收敛为一张权威词汇表下为整理后的关键条目。注意Text、Element在这里是TText、TElement的别名而不再是声明合并后的应用类型——这是与旧模型最大的语义差别类型含义落点文件TText基础文本节点{ text: string }加自定义属性interfaces/text.tsTElement基础元素节点含childreninterfaces/element.tsValue顶层文档TElement[]interfaces/editor.ts或interfaces/node.tsText/Element分别为TText、TElement的别名interfaces/text.ts、interfaces/element.tsDescendant/Ancestor/NodeTElement \| TText/Editor \| TElement/Editor \| TElement \| TTextinterfaces/node.tsTextInV/ElementInV/NodeInVV内的文本/元素/节点联合interfaces/text.ts、element.ts、node.tsTextOfE/ElementOfE/NodeOfE编辑器/节点E内的文本/元素/节点联合同上ValueOfEE[children]interfaces/editor.tsEditorMarksOfE由TextOfE派生的 mark 对象interfaces/editor.tsOperationV由NodeInV/Range键入的操作载荷interfaces/operation.tsEditorV/EditorTransactionV/EditorCommitV公开编辑器类型 / 写边界运行时 / 本地观测载荷interfaces/editor.ts、core/public-state.tsHistoryEditorV/DOMEditorV/ReactEditorV历史 / DOM / React 增强编辑器slate-history、slate-dom、slate-react各插件文件在仓库中这些派生工具已按同一命名实现于 text.ts、element.ts、node.ts含AncestorIn、AncestorOf、ChildOf、DescendantIn、DescendantOf、NodeIn、NodeOf、TNodeMatch谓词与 node-entry.ts含NodeEntryInV、NodeEntryOfE、ElementEntryOfE、TextEntryOfE、AncestorEntryOfE、DescendantEntryOfE等 entry 工具NodeEntry即[N, Path]元组编辑器查询层在 editor-api.ts 中统一为EditorApiV extends Value Value其above/nodes/next/previous/block/marks等方法的返回类型均以ElementInV、DescendantInV、TextInV、MarksInV表达。6. 文件矩阵与 G0–G5 分类为了让 1500 文件的迁移可审计计划定义了六级分类并在执行前用一条命令生成精确清单rg --files .tmp/slate-v2/packages/slate .tmp/slate-v2/packages/slate-dom .tmp/slate-v2/packages/slate-history .tmp/slate-v2/packages/slate-hyperscript .tmp/slate-v2/packages/slate-react .tmp/slate-v2/packages/slate-browser .tmp/slate-v2/site .tmp/slate-v2/docs \ | rg (\\.ts$|\\.tsx$|\\.d\\.ts$|\\.md$|\\.mdx$) \ .tmp/slate-v2-generics-file-inventory.txt各分类含义G0无需泛型改动仅验证。例如interfaces/path.ts、path-ref.ts、point-ref.ts、range-ref.ts、scrubber.ts、text-units.ts——路径/点/范围保持结构化不泛型化G1基础模型泛型迁移。包括删除types/custom-types.ts、替换interfaces/{text,element,node,editor,range,point,operation}.ts中的ExtendedType、把Editor改为EditorV、createEditor改为createEditorV()G2编辑器 API / 变换泛型线程化。editor/*.ts、transforms-node/*.ts、transforms-selection/*.ts、transforms-text/*.ts的所有查询/变换从E extends Editor派生ValueOfE、NodeOfE、ElementOfE、TextOfE、EditorMarksOfE同时保留setNodes、unwrapNodes、wrapNodes、insertNodes、delete、insertText、insertFragment的灵活入参不为凑泛型而堆语义方法G3操作/事务/提交/历史泛型。core/apply.ts的applyOperationV、core/public-state.ts的当前值/选区/marks/操作/快照/事务/提交、command-registry.ts与editor-extension.ts的E extends EditorV、slate-history的HistoryV与withHistoryV, E extends EditorV(editor: E): E HistoryEditorVG4包级编辑器包装泛型。DOMEditorV、ReactEditorV叠加于DOMEditorV与EditorV之上、HistoryEditorV以及 React 组件树中 element/text/leaf 渲染 props 从ElementOfE/TextOfE派生G5应用、文档、测试与 fixture 迁移。站点示例改为显式ExampleValue/ExampleEditor类型测试改为createEditorExampleValue()编译/运行时契约docs/concepts/12-typescript.md等文档围绕Value泛型重写changelog 保持历史原样但不得作为现行指引。原文档还给出了packages/slate、slate-dom、slate-history、slate-hyperscript、slate-react、slate-browser、站点/文档的逐文件矩阵含每一文件所属 G 级与所需工作描述本文不再逐行搬运实施时以该矩阵为核对清单。其中值得注意的边界约定slate-browser的快照属于测试协议类型而非 Slate 模型泛型须避免与EditorSnapshotV命名冲突restore-dom、large-document等内部实现保持结构化仅在公开函数触及 Slate 节点时才泛型化。7. 六批执行顺序计划把迁移切成六个批次顺序即依赖顺序Batch 0红色类型契约RED contracts。先于任何改动添加仅编译的契约文件generic-value-contract.ts、generic-editor-api-contract.ts、generic-operation-contract.ts、generic-history-contract.ts、generic-react-editor-contract.tsx配套tsconfig.generic-types.json。契约核心示例type Paragraph { type: paragraph; children: CustomText[] }; type Quote { type: quote; children: CustomText[] }; type CustomText { text: string; bold?: true; code?: true }; type CustomValue (Paragraph | Quote)[]; const editor createEditorCustomValue(); editor.update(() { editor.setNodes({ type: quote }); editor.addMark(bold, true); });契约断言ValueOftypeof editor是CustomValueElementOftypeof editor是Paragraph | QuoteTextOftypeof editor是CustomTextEditorMarksOftypeof editor接受bold/code且拒绝元素字段OperationCustomValue载荷不塌缩为无类型NodewithHistory(editor)与withReact(editor)均保留CustomValue。执行台账特别记录了一个教训Bun 的运行时测试不会对纯类型契约做检查因此 Batch 0 门禁从bun test改为tsc --noEmit。Batch 1核心模型泛型。引入Value/TElement/TText与派生助手从主模型类型移除ExtendedTypeEditor→EditorV、createEditor→createEditorV()删除核心custom-types.ts。台账显示此批还顺带完成了withHistory/withDOM/withReact的泛型编辑器保真与 React/DOM 声明合并文件的删除并把 DOM 全局声明移入独立的dom-globals.d.ts。Batch 2编辑器 API 与灵活原语泛型。查询返回类型由E extends Editor派生原语隐式目标与节点载荷类型由ValueOfE派生保留灵活 API 不增加语义方法膨胀。Batch 3操作/事务/提交/历史泛型。OperationV成为操作真相EditorTransactionV与EditorCommitV保留值类型历史消费泛型操作/提交而不泄漏方法覆盖类型扩展监听器保持E extends EditorV。Batch 4DOM 与 React 泛型编辑器。删除 React/DOM 模块增强文件线程化ReactEditorV/DOMEditorV渲染 props 从编辑器值派生浏览器内核行为不变除非类型契约暴露真实 bug。Batch 5Hyperscript、站点、文档、测试。用普通导出的应用类型替换declare module slate示例迁移到显式ExampleValue/ExampleEditorTypeScript 文档围绕Value泛型重写。Batch 6source-first 站点别名重开。此批被应用CustomTypes泄漏进包内部所阻塞须在 Batch 5 之后重开用于启用站点/根 TypeScript 源码别名并移除为旧全局自定义类型而设的 workaround。8. 质量门禁Gates计划要求每批改动通过以下门禁且强调从.tmp/slate-v2运行硬切搜索门禁rg搜索CustomTypes|ExtendedType|declare module []slate排除 dist/node_modules/site out在packages/**、site/**与现行文档中必须为零命中仅允许 changelog 的历史条目保留另搜索EditorMarks Recordstring, any|Recordstring, any主 marks 类型不得使用any类型门禁三个tsconfig.generic-types.json的bunx tsc --project ... --noEmit --pretty false全绿包门禁bunx turbo typecheck --filter./packages/slate ... --force与五个包的bun test ... --bail 1全绿站点门禁bun typecheck:site与bun typecheck最终门禁bun run lint:fix与bun run lint明确说明除非 React/浏览器运行时文件改动被证明改变了行为否则本类型计划不运行bun test:integration-local。9. 红线清单Red Flags计划把以下行为列为红线防止迁移走回头路核心泛型落地后仍把CustomTypes当临时保留——会造成两个真相来源让EditorV从环境上下文推断站点示例值——会重现当前 source-first 类型检查 bug原样照搬 Plate 的EditorMarks Recordstring, any用as unknown as Editor隐藏泛型丢失在packages/slate助手类型稳定前先行更新 React 泛型把文档/示例当作纯清理工作——旧心智模型正是在那里反复回潮把所有东西重命名为 schema-first 模型——会偏离 Plate 并惩罚迁移者。10. 完成标准与执行结果计划的完成标准CustomTypes/ExtendedType从主包与现行文档消失createEditorCustomValue()成为自定义类型键入的唯一规范入口EditorV、ValueOfE、ElementOfE、TextOfE、NodeOfE、EditorMarksOfE在 core、DOM、React、history、hyperscript、站点示例与测试中一致可用操作/事务/提交/历史/扩展类型保留V站点源码别名可在包内部不依赖应用专属全局的前提下启用上述门禁通过或每个剩余失败都有精确的非自主阻塞记录。执行台账2026-04-26记录了从启动到收尾的全过程Batch 0 产出 1502 文件的清单与五个红色契约Batch 1 建立核心/DOM/React 泛型主干并删除声明合并Batch 2 移除现行文档/站点/测试的声明合并叙事、删除旧 custom-types fixture 门禁、站点示例改用createEditorCustomValue()、启用站点源码别名并让 React hooks 对完整编辑器实例类型泛型化最终状态为全部泛型契约变绿、bun typecheck全绿、各包测试全绿、lint 全绿、主包源码搜索零CustomTypes/ExtendedType/declare module slate命中仓库级搜索仅剩有意保留的历史 changelog 条目。一个被明确否决的战术是让每个内部实例方法都完美泛型——那会造成 TypeScript 方差爆炸并削弱实现持久边界是泛型公开/静态 API 结构化宽松的运行时内部另一个否决是为满足BaseElement.type在 hyperscript 中做运行时变更类型桥接保持仅 castfixture 输出不变。结语这份计划的价值在于它同时给出了做什么与不做什么Slate v2 的泛型方向不是发明而是把 Plate 已经验证的 Value 优先模型系统性内化并仅在 marks、操作/提交线程化、schema 写作别名三处做出明确超越同时用 G0–G5 分类、六批顺序、硬性门禁与红线清单把 1500 文件的迁移变成了可审计、可回滚、可验证的工程流程。对想要理解 Plate 泛型模型或迁移自定义类型方案的开发者而言仓库 packages/slate/src/interfaces 下的editor、element.ts、text.ts、node.ts、node-entry.ts、operation.ts与 create-editor.ts、create-editor.spec.ts 即是这套模型最直接、可编译验证的活教材。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考