富文本编辑器技术选型,到底是 Prosemirror 还是 Tiptap 好 ❓❓❓

在前端开发中,撤销和重做功能是提升用户体验的重要特性。无论是文本编辑器、图形设计工具,还是可视化搭建平台,都需要提供历史操作的回退和前进能力。这个功能看似简单,但实现起来需要考虑性能、内存占用、用户体验等多个方面。

在构建富文本编辑器时,Tiptap 和 ProseMirror 是两个常见的技术选择。两者都强大且灵活,但它们在设计理念、易用性、扩展性等方面存在差异。对于开发者来说,选择合适的工具对于项目的成功至关重要。本文将深入探讨两者的异同,并通过实际代码示例帮助你理解它们的差异,从而根据具体需求做出决策。

ProseMirror 的优势与挑战

ProseMirror 是一个 JavaScript 库,用于构建复杂的富文本编辑器。它的设计非常底层,提供了一个高效且灵活的文档模型,开发者可以完全控制编辑器的行为和界面。ProseMirror 本身并不提供任何 UI 或组件,而是一个核心库,开发者需要自行实现具体的编辑器功能。

作为一个底层框架,ProseMirror 允许开发者完全控制编辑器的各个方面,包括文档结构、输入行为、UI 样式等。它提供了丰富的 API,可以处理复杂的编辑需求,如数学公式、代码块、图片、链接等。开发者可以为几乎任何功能编写插件,并且可以在已有插件的基础上进行二次开发。基于虚拟 DOM
的设计,使其在大文档和复杂结构下能够提供较高的性能。

然而,由于其底层设计,ProseMirror 的 API 复杂,学习曲线陡峭。开发者需要深入理解其文档模型、事务管理、节点和视图的关系。由于不提供任何 UI 组件,开发者需要从零开始构建编辑器的界面和交互,配置和初始化过程也较为复杂,需要手动处理许多底层逻辑。

ProseMirror 基础使用示例

首先需要安装必要的包:

npm install prosemirror-state prosemirror-view prosemirror-model prosemirror-schema-basic prosemirror-schema-list prosemirror-commands

创建一个基本的 ProseMirror 编辑器需要配置 schema、state 和 view:

import { EditorState } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Schema, DOMParser } from "prosemirror-model"; import { schema } from "prosemirror-schema-basic"; import { addListNodes } from "prosemirror-schema-list"; import { exampleSetup } from "prosemirror-example-setup"; // 扩展基础 schema,添加列表支持 const mySchema = new Schema({ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"), marks: schema.spec.marks, }); // 创建编辑器状态 const state = EditorState.create({ schema: mySchema, plugins: exampleSetup({ schema: mySchema }), }); // 创建编辑器视图 const view = new EditorView(document.querySelector("#editor"), { state, });

如果需要添加自定义命令,比如一个格式化工具条,需要手动实现:

import { toggleMark } from "prosemirror-commands"; import { schema } from "prosemirror-schema-basic"; // 创建加粗命令 const toggleBold = toggleMark(schema.marks.strong); // 手动创建工具栏按钮 function createToolbar(view) { const toolbar = document.createElement("div"); toolbar.className = "toolbar"; const boldBtn = document.createElement("button"); boldBtn.textContent = "Bold"; boldBtn.onclick = () => { toggleBold(view.state, view.dispatch); view.focus(); }; toolbar.appendChild(boldBtn); return toolbar; }

ProseMirror 自定义插件示例

创建一个自定义插件需要理解 ProseMirror 的插件系统:

import { Plugin } from "prosemirror-state"; // 创建一个字符计数插件 function characterCountPlugin() { return new Plugin({ view(editorView) { const counter = document.createElement("div"); counter.className = "char-counter"; const updateCounter = () => { const text = editorView.state.doc.textContent; counter.textContent = `字符数: ${text.length}`; }; updateCounter(); return { update(view) { updateCounter(); }, destroy() { counter.remove(); }, }; }, }); } // 使用插件 const state = EditorState.create({ schema: mySchema, plugins: [characterCountPlugin(), ...exampleSetup({ schema: mySchema })], });

Tiptap 的便捷开发

Tiptap 是基于 ProseMirror 构建的富文本编辑器框架,它简化了 ProseMirror 的复杂性,提供了现成的 UI 组件和更易于使用的API。Tiptap 旨在让开发者能够快速实现丰富的富文本编辑器,同时保持较高的灵活性和扩展性。

Tiptap 提供了简洁的 API,开发者不需要深入学习 ProseMirror 的底层概念即可实现基本的富文本编辑功能。它通过封装 ProseMirror 的复杂性,使得开发过程更加直观和简便。开箱即用的 UI 组件,如文本格式化、列表、图片插入等,极大地方便了开发者的使用,减少了开发时间。清晰的文档和活跃的开源社区,也为开发者提供了良好的支持和资源。虽然 Tiptap 进行了封装,但它仍然保留了 ProseMirror 的插件系统,开发者可以根据需要定制功能,并且可以轻松地集成其他插件。此外,Tiptap 可以与 Yjs
或其他 CRDT
库结合,支持实时协作编辑功能,这是 ProseMirror 本身不具备的特性。

不过,由于 Tiptap 封装了 ProseMirror 的很多底层功能,灵活性相对较低。对于一些需要极高自定义的需求,Tiptap 可能不如 ProseMirror 灵活。虽然在大多数情况下性能良好,但在处理超大文档或复杂操作时,性能可能不如直接使用 ProseMirror。

Tiptap 基础使用示例

Tiptap 的安装和使用相对简单:

npm install @tiptap/react @tiptap/starter-kit @tiptap/pm

在 React 中使用 Tiptap:

import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; function TiptapEditor() { const editor = useEditor({ extensions: [StarterKit], content: "<p>Hello World!</p>", }); if (!editor) { return null; } return ( <div> <div className="toolbar"> <button onClick={() => editor.chain().focus().toggleBold().run()} disabled={!editor.can().chain().focus().toggleBold().run()} className={editor.isActive("bold") ? "is-active" : ""} > Bold </button> <button onClick={() => editor.chain().focus().toggleItalic().run()} disabled={!editor.can().chain().focus().toggleItalic().run()} className={editor.isActive("italic") ? "is-active" : ""} > Italic </button> <button onClick={() => editor.chain().focus().toggleBulletList().run()} className={editor.isActive("bulletList") ? "is-active" : ""} > Bullet List </button> </div> <EditorContent editor={editor} /> </div> ); }

Tiptap 的 Vue 版本同样简洁:

<template> <div> <div class="toolbar"> <button @click="editor.chain().focus().toggleBold().run()" :disabled="!editor.can().chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }" > Bold </button> <button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }" > Italic </button> </div> <editor-content :editor="editor" /> </div> </template> <script> import { useEditor, EditorContent } from "@tiptap/vue-3"; import StarterKit from "@tiptap/starter-kit"; export default { components: { EditorContent, }, setup() { const editor = useEditor({ extensions: [StarterKit], content: "<p>Hello World!</p>", }); return { editor }; }, }; </script>

Tiptap 扩展功能示例

Tiptap 支持多种扩展,添加图片功能非常简单:

import Image from "@tiptap/extension-image"; import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; function EditorWithImage() { const editor = useEditor({ extensions: [ StarterKit, Image.configure({ inline: true, allowBase64: true, }), ], }); const addImage = () => { const url = window.prompt("图片URL"); if (url) { editor.chain().focus().setImage({ src: url }).run(); } }; return ( <div> <button onClick={addImage}>添加图片</button> <EditorContent editor={editor} /> </div> ); }

创建自定义扩展也很直观:

import { Extension } from "@tiptap/core"; import { Plugin } from "prosemirror-state"; const CharacterCount = Extension.create({ name: "characterCount", addProseMirrorPlugins() { return [ new Plugin({ view(editorView) { const counter = document.createElement("div"); counter.className = "char-counter"; const updateCounter = () => { const text = editorView.state.doc.textContent; counter.textContent = `字符数: ${text.length}`; }; updateCounter(); return { update(view) { updateCounter(); }, destroy() { counter.remove(); }, }; }, }), ]; }, }); // 使用自定义扩展 const editor = useEditor({ extensions: [StarterKit, CharacterCount], });

Tiptap 实时协作示例

Tiptap 与 Yjs 集成实现实时协作非常简单:

npm install yjs y-prosemirror @tiptap/extension-collaboration @tiptap/extension-collaboration-cursor

import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import Collaboration from "@tiptap/extension-collaboration"; import CollaborationCursor from "@tiptap/extension-collaboration-cursor"; import * as Y from "yjs"; import { WebrtcProvider } from "y-webrtc"; // 创建 Yjs 文档和提供者 const ydoc = new Y.Doc(); const provider = new WebrtcProvider("room-name", ydoc); function CollaborativeEditor() { const editor = useEditor({ extensions: [ StarterKit, Collaboration.configure({ document: ydoc, }), CollaborationCursor.configure({ provider, }), ], }); return <EditorContent editor={editor} />; }

从代码看差异

让我们通过实现一个带工具栏的编辑器来对比两者的代码复杂度:

在 ProseMirror 中,需要手动管理所有状态和命令:

import { EditorState, Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { schema } from "prosemirror-schema-basic"; import { toggleMark } from "prosemirror-commands"; const state = EditorState.create({ schema }); const toolbarPlugin = new Plugin({ view(editorView) { const toolbar = document.createElement("div"); toolbar.className = "toolbar"; const boldBtn = document.createElement("button"); boldBtn.textContent = "B"; boldBtn.onclick = (e) => { e.preventDefault(); const { state, dispatch } = editorView; const command = toggleMark(schema.marks.strong); if (command(state, dispatch)) { editorView.focus(); } }; toolbar.appendChild(boldBtn); document.body.insertBefore(toolbar, editorView.dom); return { destroy() { toolbar.remove(); }, }; }, }); const view = new EditorView(document.querySelector("#editor"), { state: EditorState.create({ schema, plugins: [toolbarPlugin], }), });

而在 Tiptap 中,相同的功能实现更加简洁:

const editor = useEditor({ extensions: [StarterKit], }); return ( <div> <button onClick={() => editor.chain().focus().toggleBold().run()} className={editor.isActive("bold") ? "is-active" : ""} > B </button> <EditorContent editor={editor} /> </div> );

如何做出选择

选择 Tiptap 还是 ProseMirror,关键在于项目需求和开发团队的技术能力。

如果你的目标是快速构建一个功能丰富、用户友好的富文本编辑器,且不希望花费过多时间在底层细节上,Tiptap 是一个理想的选择。它提供了简洁的 API 和现成的 UI 组件,可以快速启动和开发。如果你的编辑器需要一些定制功能,但不需要完全控制每个底层细节,Tiptap 提供了足够的灵活性,同时保持了开发的简便性。如果需要实现多人实时协作,Tiptap 内建的对 Yjs 等库的支持可以简化实现过程。

如果你需要完全控制编辑器的行为、界面和性能,ProseMirror 提供了更高的自由度。它适合那些有特定需求的项目,比如自定义文档结构、输入行为或非常复杂的编辑操作。在处理非常大的文档或需要极高性能的场景下,ProseMirror 能提供更好的优化和性能。如果你的项目需要完全自定义插件,或者你想对编辑器进行深度定制,ProseMirror 提供了更高的灵活性

性能考虑

对于大文档处理,ProseMirror 提供了更细粒度的控制:

// ProseMirror 中可以精确控制更新 const state = EditorState.create({ schema, plugins: [ // 可以精确控制哪些插件启用 // 可以自定义更新逻辑 new Plugin({ state: { init() { return {}; }, apply(tr, value) { // 自定义状态更新逻辑 return value; }, }, }), ], });

而 Tiptap 虽然性能良好,但在极端场景下可能不如直接使用 ProseMirror 优化:

// Tiptap 的性能优化选项 const editor = useEditor({ extensions: [StarterKit], editorProps: { attributes: { class: "prose prose-sm sm:prose lg:prose-lg xl:prose-2xl mx-auto focus:outline-none", }, // 可以传递 ProseMirror 的原生配置 }, // 但仍然受到封装层的限制 });

生态系统和社区支持

Tiptap 拥有丰富的扩展生态系统:

# Tiptap 官方扩展 npm install @tiptap/extension-image npm install @tiptap/extension-link npm install @tiptap/extension-table npm install @tiptap/extension-code-block-lowlight npm install @tiptap/extension-placeholder npm install @tiptap/extension-character-count npm install @tiptap/extension-typography

而 ProseMirror 的插件需要通过 prosemirror-* 包系列来获取,或者自己实现。官方提供了基础插件,但高级功能需要社区插件或自行开发。

实际项目场景建议

对于博客平台、内容管理系统、笔记应用等常见场景,Tiptap 通常是最佳选择。它的快速开发和丰富的功能足以满足大多数需求。代码示例展示了如何在几分钟内搭建一个功能完整的编辑器。

对于需要特殊文档结构(如学术论文编辑器、代码编辑器、专业排版工具)或对性能有极致要求的场景,ProseMirror 提供了必要的底层控制能力。但需要投入更多时间学习其 API 和概念。

如果你的团队时间有限,或者希望快速迭代,Tiptap 是明智的选择。如果团队有富文本编辑器开发经验,或者有充足时间进行深度定制,ProseMirror 可以带来更高的灵活性和性能。

总结

Tiptap 是一个基于 ProseMirror 的富文本编辑器框架,适合需要快速开发、易用且功能丰富的场景。它封装了 ProseMirror 的复杂性,让开发者能够专注于业务逻辑,而无需关心底层实现细节。通过本文的代码示例可以看出,Tiptap 的 API 设计更加直观,学习曲线平缓,适合大多数项目需求。

ProseMirror 则是一个底层框架,适合那些需要完全控制文档结构、编辑行为和性能优化的高级开发者。它更灵活,但学习曲线较陡峭,适合复杂或定制化需求较强的项目。从代码示例中可以看到,使用 ProseMirror 需要处理更多的底层细节,但同时也获得了更高的控制权。

如果你的项目需要快速构建编辑器并具备一定的自定义能力,Tiptap 是一个更为理想的选择。而如果你的项目需要完全的定制化和高性能处理,ProseMirror 将更符合你的需求。最终的选择应基于你的开发需求、项目规模以及团队的技术能力。建议通过实际代码尝试两者,根据你的具体场景做出最适合的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1155097.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【技术收藏】预训练数据选择革命:MATES等6大方法让LLM性能提升,计算量减半

该文系统综述了大语言模型预训练与后训练的数据选择方法&#xff0c;包括模型影响力驱动的MATES、质量与多样性平衡的Quad/QuaDMix/ODiS、多策略集成的multi-actor机制、结构化知识驱动的MASS、任务相关性驱动的BETR&#xff0c;以及后训练中的在线离线数据选择技术。这些方法通…

导师严选2026 TOP9 AI论文写作软件:专科生毕业论文必备测评

导师严选2026 TOP9 AI论文写作软件&#xff1a;专科生毕业论文必备测评 2026年AI论文写作软件测评&#xff1a;为何值得专科生关注 随着人工智能技术的不断进步&#xff0c;AI论文写作工具逐渐成为学术研究中不可或缺的辅助工具。对于专科生而言&#xff0c;撰写毕业论文不仅是…

5V/4A单通道高频率低侧替代LM5114GaN增强型驱动

概述&#xff1a;PC1001 是一款单通道高速驱动器&#xff0c;具有 5V 输出和专用增强型氮化镓&#xff08;GaN&#xff09;场效应晶体管&#xff08;FET&#xff09;驱动功能。PC1001 可提供非对称峰值电流驱动能力&#xff0c;源电流为 1.4A&#xff0c;灌电流为 4A&#xff0…

多平台社交媒体管理工具优选方案:科握凭AI创作+合规激励,引爆声量销售双增长

一、行业趋势&#xff1a;多平台整合与一线赋能成社媒营销关键普华永道《中国内地及香港地区奢侈品市场洞察&#xff1a;“重塑客户价值&#xff0c;实现可持续增长”》显示&#xff0c;亚太地区已成为全球奢侈品市场的重要增长引擎&#xff0c;预计 2025 年年均复合增速 11%。…

2026 年CBAM 要不要现在就做?先说结论

如果你是出口欧盟的企业&#xff0c; 2026 年你最容易犯的错误不是“什么都没做”&#xff0c; 而是——在错误的阶段&#xff0c;做了错误的事情。 我先把结论放在最前面&#xff1a; 2026 年&#xff0c;大多数企业不需要“启动完整 CBAM 项目”&#xff0c; 但必须开始…

【深度学习】YOLO 模型核心部署格式全解析(PyTorch/ONNX/TensorRT/TFLite)

本位旨在全面掌握 YOLO 模型的四大核心部署格式&#xff08;PyTorch 模型、ONNX 格式、TensorRT 引擎、TFLite 格式&#xff09;&#xff0c;本文将从格式特性、适用场景、转换实操、推理部署、优劣对比五个维度展开&#xff0c;以 YOLOv8 为例&#xff08;v5 通用&#xff09;…

章泽天开播客,网友喊话对话刘强东!列好了一堆问题……

这几年&#xff0c;播客是真的火了&#xff0c;比如罗永浩的十字路口&#xff0c;自开播以来&#xff0c;每一期都会贡献无数个热搜&#xff0c;这也让罗永浩成为长盛不衰的顶流网红。在罗永浩之外&#xff0c;各路有影响力的IP&#xff0c;不是在搞播客&#xff0c;就是在搞播…

基于随机波动率模型与马尔可夫链蒙特卡洛方法的指数期权波动率曲面拟合改进

功能说明 本代码实现随机波动率模型&#xff08;Stochastic Volatility, SV&#xff09;结合Johnson-Johnson&#xff08;JJ&#xff09;分布假设&#xff0c;通过马尔可夫链蒙特卡洛&#xff08;Markov Chain Monte Carlo, MCMC&#xff09;方法对指数期权隐含波动率曲面进行动…

波动率期限结构调整策略在指数期权日历价差中的应用研究

功能与作用说明 本策略通过构建不同到期日的指数期权组合&#xff0c;利用隐含波动率期限结构特征获取套利收益。核心功能包括&#xff1a;1&#xff09;动态调整远近月合约持仓比例&#xff1b;2&#xff09;基于波动率曲面变化进行头寸再平衡&#xff1b;3&#xff09;对冲标…

【深度学习】YOLO 模型典型应用场景分析(安防 / 自动驾驶 / 工业质检 / 医疗影像 / 智慧城市)

YOLO&#xff08;You Only Look Once&#xff09;系列模型凭借 实时性强、精度高、部署灵活 的核心优势&#xff0c;已成为计算机视觉领域目标检测任务的主流算法。尤其在 YOLOv8/v11 等新版本中&#xff0c;通过轻量化设计、多尺度检测、高效推理优化&#xff0c;进一步适配了…

租房新时代:一键解锁理想居所的小程序革命

在数字化租房需求爆发的当下&#xff0c;租房小程序凭借 “即用即走” 的轻量化体验&#xff0c;成为连接租客与房源的核心载体。一款稳定、高效、用户体验佳的租房小程序&#xff0c;背后需要一套科学的技术架构与严谨的开发流程支撑。本文将从技术选型、核心功能实现、性能优…

电缆护层保护器工作方式详解

电缆护层保护器的工作原理电缆护层保护器主要用于防止电力电缆金属护层&#xff08;如铝护套或铅护套&#xff09;因感应电压或故障电流导致的过电压损坏。其核心功能是通过限制护层电压在安全范围内&#xff0c;同时为故障电流提供低阻抗通路。主要工作方式限压保护 护层保护器…

QM系列闪测仪效率革新 实现鼠标产品高品质人机交互

​在消费电子领域&#xff0c;鼠标作为高频率、高精度的直接人机交互界面&#xff0c;其产品品质直接决定了用户体验与品牌声誉。对于鼠标制造而言&#xff0c;从精密注塑、橡胶硫化到最终组装&#xff0c;每一个环节的尺寸与形位公差控制都至关重要。一、为何必须对鼠标核心部…

用AI一句话生成应用,还带后端代码?

作为一名对工程化有洁癖的全栈开发&#xff0c;我最近一直在观察 AI Coding 领域的进化。市面上大多数 AI 工具&#xff08;比如 Copilot&#xff09;本质上还是个“高级补全器”&#xff0c;能写函数&#xff0c;但很难搞定整体架构。直到昨天&#xff0c;我在测试一款名为 Ly…

Agentgateway 代理 MCP 流量初探

关于Agentgateway代理MCP流量&#xff0c;它的核心定位是为AI Agent场景&#xff08;特别是MCP/A2A协议&#xff09;提供企业级的治理、安全、可观测与协议转换。&#x1f4cc; 核心概念 MCP (模型上下文协议)&#xff1a;一个标准化协议&#xff0c;让AI Agent能以统一、结构化…

计算机毕业设计springboot体育赛事管理系统 基于SpringBoot的高校体育竞赛综合管理平台 SpringBoot+Vue的校园运动会与智慧赛事运营系统

计算机毕业设计springboot体育赛事管理系统326429le &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。当“全民健身”与“数字中国”双重浪潮交汇&#xff0c;传统的手工排赛程、纸…

How transferable are features in deep neural networks

随着读取的论文量增加&#xff0c;发现研读论文&#xff0c;更重要的是作者的思维方式&#xff0c;以及自己对深度学习网络的理解&#xff0c;作者是怎么理解当前的网络的&#xff0c;他是从哪些角度发现当前的问题的&#xff0c;作者有事怎么逐步分析&#xff0c;进而引出自己…

2026年1月房产中介管理系统哪家好用

随着房产中介行业数字化转型的不断深入&#xff0c;一套高效实用的房产中介管理系统已成为提升运营效率、增强市场竞争力的核心支撑。无论是个人经纪人、夫妻小店&#xff0c;还是中大型连锁中介公司&#xff0c;都需要适配自身业务场景的管理工具。2026年伊始&#xff0c;市面…

测试网络韧性:延迟与丢包模拟

第一章 网络韧性测试的核心价值 在分布式架构主导的软件生态中&#xff0c;网络故障引发的级联失效已成为系统崩溃的首要诱因。Gartner 2025年报告指出&#xff0c;73%的重大线上事故源于未经验证的网络边界场景。通过精准模拟延迟抖动、包序错乱及协议层丢包&#xff0c;测试…

3D应用丨光子精密解锁多个高难度3D相机拼接检测

在高端制造与精密检测领域&#xff0c;面对尺寸庞大、结构复杂或要求全尺寸测量的工件&#xff0c;3D线激光轮廓测量仪的拼接检测技术已成为不可或缺的解决方案。然而&#xff0c;稳定可靠的拼接检测绝非简单叠加 3D 相机就能实现。在真实工业场景中的应用&#xff0c;常具体化…