在 Next.js App Router 中集成 Slate.js:构建富文本编辑器实战(基于 with-slate 官方示例)

发布时间:2026/9/8 23:21:59
在 Next.js App Router 中集成 Slate.js:构建富文本编辑器实战(基于 with-slate 官方示例) 在 Next.js App Router 中集成 Slate.js构建富文本编辑器实战基于 with-slate 官方示例【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js本文围绕当前仓库中的 examples/with-slate 官方示例展开讲解如何在 Next.jsApp Router应用中引入 Slate.js 构建可编辑的富文本区域并演示如何将编辑器内部状态AST 数据通过 Route Handler 提交到服务端。读完本文你将掌握create-next-app示例引导流程、Slate 与 React 的接入骨架、自定义 TypeScript 类型扩展方法以及编辑器变更 → 状态持久化的完整链路设计。示例概览这个例子到底做了什么with-slate示例本质上是一个最小可运行的 Slate 编辑器集成模板其核心能力包括基于 Slate 的 React 绑定slate-react渲染一个可编辑区域监听编辑器内容变化仅在发生真正的 AST 变更而非光标选择变化时将最新编辑器状态 POST 到服务端接口通过 Route Handlerapp/api/editor-state/route.ts接收并示意性保存编辑器状态提供自定义类型声明types/slate.d.ts让 Slate 的自定义 Element / Text 结构获得完整 TypeScript 类型推断。整个工程采用 App Router 目录约定源码由页面组件、API 路由、类型声明与基础布局四部分组成结构清晰、易于在此基础上扩展真实业务。如何快速启动该示例示例 README 提供了通过create-next-app一键引导的方式。create-next-app的 CLI 支持-e, --example example-name|github-url参数见 packages/create-next-app/index.tswith-slate正是仓库内众多官方示例名称之一。三种包管理器对应命令如下npx create-next-app --example with-slate with-slate-appyarn create next-app --example with-slate with-slate-apppnpm create next-app --example with-slate with-slate-app命令执行后会在当前目录创建名为with-slate-app的新工程其内容即 examples/with-slate 目录下代码的副本。引导完成后进入工程目录并启动开发服务器cd with-slate-app npm run dev示例工程在 package.json 中声明了三个常用脚本next驱动的dev开发模式、next build驱动的build生产构建、next start驱动的start运行生产构建产物。浏览器访问http://localhost:3000即可在页面中看到一个可编辑的纯文本区域placeholder 为 Enter some plain text...。依赖清单与版本基线工程在 package.json 中锁定了一批依赖理解它们各自的职责有助于后续改造依赖作用nextlatestNext.js 框架本体提供 App Router、Route Handler 等能力react/react-dom^18.3.xReact 运行时Slate 的 React 绑定依赖其渲染模型slate^0.103.0编辑器核心不可变数据模型、操作operation系统、插件化能力slate-react^0.107.1React 绑定提供Slate、Editable、withReact等组件/装饰器slate-history0.100.0历史记录插件提供撤销/重做能力示例中用于类型合成开发依赖包括typescript^5.5.4及types/node、types/react、types/react-dom。从依赖构成可以看出示例刻意保持了最小集合渲染一个可编辑区域只需要slate与slate-react而slate-history主要服务于类型层面的编辑器合成见下文类型声明部分。源码结构拆解一个 Slate 编辑器如何长在 Next.js 页面上页面组件Slate 编辑器的挂载点app/page.tsx 是整个示例的核心第一行use client声明它属于客户端组件——这是必要前提Slate 编辑器的交互、选区管理与 React 生命周期都依赖浏览器环境。组件内部的实现要点如下创建编辑器通过useState(() withReact(createEditor()))惰性创建编辑器实例用withReact装饰原生createEditor()返回的编辑器使其能与slate-react的 React 渲染管线协同。注意编辑器实例必须稳定不能每次渲染重新创建这正是放在useState初始化器中的原因。初始内容initialValue是 Slate 文档树的 JSONAST形式示例中是一个仅包含单个段落、单段文本的最小文档const initialValue: Descendant[] [ { children: [ { text: This is editable plain text, just like a textarea! }, ], }, ];渲染组合用Slate editor{editor} initialValue{initialValue} onChange{...}包裹Editable placeholderEnter some plain text... /。Slate提供上下文Editable负责渲染可编辑 DOM 区域placeholder在内容为空时展示占位提示。变更监听与保存触发onChange回调接收最新文档值value但并非每次回调都值得落库——因为用户仅移动光标只产生set_selection操作时也会触发 onChange。示例通过检查editor.operations判断是否发生了真正的文档结构变化onChange{async (value) { const isAstChange editor.operations.some( (op) set_selection ! op.type, ); if (isAstChange) { const responseData await saveEditorState(value); console.log(Send editor state to the server, responseData); } }}这里体现了 Slate 的一个核心设计所有编辑行为都会归结为一系列 operation如插入文本、拆分节点、设置选区等开发者可以依据 operation 类型决定响应策略。源码注释还特别提醒You might want to debounce the following call!——在实际生产场景中每次键盘输入都发起一次 POST 显然不经济通常应引入防抖debounce或节流throttle后再保存。保存函数把编辑器状态送出浏览器同文件中定义了一个基于fetch的保存函数async function saveEditorState(editorState: Descendant[]) { const response await fetch(/api/editor-state/, { method: POST, body: JSON.stringify(editorState), }); return response.json(); }它将Descendant[]类型的文档树 JSON 序列化后 POST 到同源下的/api/editor-state/路由返回响应中的 JSON 数据。Descendant[]是 Slate 文档树的标准类型——一个由 Element 与 Text 节点组成的嵌套结构天然可序列化这也是 Slate 文档状态适合做持久化、协作、回放的基础。Route Handler服务端接收与落库占位app/api/editor-state/route.ts 是 Next.js App Router 中的 Route Handler导出一个异步POST函数export async function POST(req: Request) { const editorState await req.json(); console.log(TODO: Save editorState on the server, editorState); return Response.json({ status: ok, }); }它接收请求体 JSON即前端发来的文档树目前仅在服务端控制台打印出 TODO: Save editorState on the server 及数据随后返回{ status: ok }。这是一个非常典型的可运行占位设计完整演示了客户端 → 服务端的状态传输链路同时把真正的持久化写入数据库、对象存储等留给开发者按业务实现。若需要接入数据库只需在此函数中替换 console.log 为数据写入逻辑即可。TypeScript 深度整合扩展 Slate 自定义类型Slate 从设计上就鼓励开发者定义自己的文档模型自定义 Element / Text / Editor 形状。为了让这些自定义结构获得类型安全需要修改slate模块的声明。types/slate.d.ts 演示了标准做法import { BaseEditor } from slate; import { ReactEditor } from slate-react; import { HistoryEditor } from slate-history; type CustomElement { type?: paragraph; children: CustomText[] }; type CustomText { text: string; bold?: true }; declare module slate { interface CustomTypes { Editor: BaseEditor ReactEditor HistoryEditor; Element: CustomElement; Text: CustomText; } }关键点逐条解析CustomElement定义节点结构type可选且目前仅支持paragraphchildren必须是CustomText[]后续扩展为嵌套 Element 时这里可改为 Element 与 Text 的联合类型CustomText定义文本节点除text字段外预留了bold?: true可选属性为将来实现加粗等内联格式埋下伏笔Editor类型由BaseEditorslate 核心、ReactEditorslate-react与HistoryEditorslate-history交叉合成使编辑器实例同时具备 React 绑定与历史记录插件的完整类型通过declare module slate中的CustomTypes接口将上述自定义类型注入 Slate 的全局类型系统从此Editor、Element、Text相关 API 的推断都会自动带上自定义结构。要让该声明文件生效需确认 tsconfig.json 的include覆盖了它示例中的include为[next-env.d.ts, **/*.ts, **/*.tsx]因此types/slate.d.ts会被纳入编译。这是把 Slate 示例工程类型化、工程化的重要一步——没有它所有节点访问都将退化为宽松的 any 推断。从示例到真实业务扩展方向与注意事项基于上述最小骨架扩展为真实富文本编辑器通常需要处理几类问题示例代码与目录结构已经为它们预留了明确的落点丰富编辑器能力在 app/page.tsx 中引入插件装饰器如withHistory、withImages等为createEditor()的结果叠加多层能力同时扩展 types/slate.d.ts 中的CustomElement/CustomText联合类型以匹配新增节点。实现工具栏与快捷键仍以客户端组件为主在Editable外层渲染工具按钮通过editor的 transform API如insertText、toggleMark响应用户操作。落库与回放在 app/api/editor-state/route.ts 中把console.log替换为真实的数据写入需要重新载入历史内容时可将从数据库读取的 JSON 直接作为initialValue传入Slate组件。性能考量服务端写入前务必防抖debounce编辑器请求同时对Descendant[]载荷做体积控制或差异同步。服务端渲染边界由于 Slate 依赖 DOM 与浏览器选区页面入口必须使用use client如数据源来自数据库建议在 Server Component 中完成读取再以 props 传给该客户端组件。总结with-slate示例以不到百行的核心代码完整串联起客户端编辑器渲染 → operation 驱动的变更判定 → fetch 提交 → Route Handler 接收 → 服务端落库占位这一富文本功能的主链路。通过阅读 app/page.tsx、app/api/editor-state/route.ts 与 types/slate.d.ts你不仅能快速跑起一个 Slate 编辑器更可以把它作为起点基于 Next.js App Router 与现代 TypeScript 实践搭建出属于自己业务的完整富文本编辑系统。【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考