React中Tiptap编辑器编辑超3000字页面冻结无响应原因排查
React中Tiptap编辑器内容超3000字时页面冻结无响应的原因排查
问题描述
在React项目中使用Tiptap编辑器,当编辑内容超过3000字时,页面出现冻结、无响应的情况。
补充信息
- PS(1):猜测问题可能出在
useState上,大量使用useState并基于它渲染组件,例如:
{ canPost && <Component/>}
- PS(2):移除使用
useState的组件后,页面仍然崩溃。
编辑器扩展代码
//// Node TipTap Extensions import Document from "@tiptap/extension-document"; import Paragraph from "@tiptap/extension-paragraph"; import Text from "@tiptap/extension-text"; // mark tipap extension import Bold from "@tiptap/extension-bold"; import Code from "@tiptap/extension-code"; import Highlight from "@tiptap/extension-highlight"; import Italic from "@tiptap/extension-italic"; import Link from "@tiptap/extension-link"; import Strike from "@tiptap/extension-strike"; import Underline from "@tiptap/extension-underline"; // other tiptap extensions import Dropcursor from "@tiptap/extension-dropcursor"; import Gapcursor from "@tiptap/extension-gapcursor"; import History from "@tiptap/extension-history"; import Placeholder from "@tiptap/extension-placeholder"; import Related from "@/lib/tiptap/related"; import { TipTapImage } from "@/lib/tiptap/image"; // Block TipTap Extensions import Ads from "@/lib/tiptap/ads"; import Blockquote from "@tiptap/extension-blockquote"; import CodeBlock from "@tiptap/extension-code-block"; import BulletList from "@tiptap/extension-bullet-list"; import ListItem from "@tiptap/extension-list-item"; import HardBreak from "@tiptap/extension-hard-break"; import Heading from "@tiptap/extension-heading"; import HorizontalRule from "@tiptap/extension-horizontal-rule"; import OrderedList from "@tiptap/extension-ordered-list"; import Table from "@tiptap/extension-table"; import TableCell from "@tiptap/extension-table-cell"; import TableHeader from "@tiptap/extension-table-header"; import TableRow from "@tiptap/extension-table-row"; import Youtube from "@tiptap/extension-youtube"; import Video from "@/lib/tiptap/video"; export const editorExtensions = [ Ads, Bold, Blockquote, BulletList, Code, CodeBlock, Document, Dropcursor, Gapcursor, HardBreak, Heading.configure({ levels: [2, 3, 4, 5, 6], }), HorizontalRule, Highlight, History, Italic, Link.configure({ openOnClick: false, HTMLAttributes: { rel: null, }, }), ListItem, OrderedList, Paragraph, Placeholder.configure({ placeholder: "Mulai menulis artikel dengan membaca bismillah dan akhiri dengan kata alhamdulillah, insya allah berkah...", }), Related, Strike, TableRow, TableCell, TableHeader, Table.configure({ resizable: true, }), Text, TipTapImage.configure({ HTMLAttributes: { loading: "lazy", width: "auto", height: "auto", sizes: "(max-width: 500px) 320px, (max-width: 1320px) 640px, 1280px", class: `h-auto w-full`, }, }), Underline, Video, Youtube.configure({ HTMLAttributes: { class: "youtube", loading: "lazy", height: "320", width: "640", }, }), ];
useEditor配置代码
const editor = useEditor({ extensions: editorExtensions, content: data?.content || "", editorProps: { attributes: { class: "prose w-full focus:outline-none prose-primary-600 min-h-[320px] max-w-none", }, }, });
问题原因及解决方案
1. 自定义扩展的性能瓶颈
你使用了多个自定义扩展(Ads、Related、TipTapImage、Video),这些扩展如果未做性能优化,比如在update或render阶段执行大量同步计算,内容增多时会阻塞主线程。
- 排查方向:检查自定义扩展的
render、update或addNodeView方法,避免在这些阶段执行循环遍历、DOM操作或数据转换等耗时操作。 - 优化方案:对复杂计算做防抖/节流处理,或把非必要逻辑移到异步任务中。
2. Table扩展的resize性能问题
启用了Table.configure({ resizable: true })后,当表格行列数多且内容量大时,resize监听会频繁触发重计算,导致页面卡顿。
- 优化方案:暂时关闭表格可调整大小功能,或给resize事件添加节流处理;若必须保留resize,限制表格最大行列数。
3. Prose样式的重排重绘开销
编辑器容器使用prose类(Tailwind prose插件),内容量巨大时,prose的全局样式会导致大量DOM元素重排重绘,尤其每次编辑触发内容更新时。
- 优化方案:简化prose样式,移除不必要的全局规则;或自定义编辑器基础样式,避免使用过于复杂的全局样式集合。
4. Tiptap历史记录缓存过载
History扩展默认缓存所有编辑操作,内容超3000字时,历史记录内存占用显著增加,每次编辑都会触发序列化/反序列化,阻塞主线程。
- 优化方案:配置
History扩展的depth参数,限制历史记录最大条数:
History.configure({ depth: 20 // 仅保留最近20次操作记录 })
5. 组件不必要重渲染
虽移除部分useState相关组件,但仍可能存在其他组件在编辑器内容变化时频繁重渲染,比如父组件未用React.memo包裹,或依赖项处理不当。
- 排查方向:用React DevTools的Profiler工具,查看编辑时频繁重渲染的组件;确认
useEditor的配置参数稳定(当前editorExtensions为导出常量,无问题,若动态生成需用useMemo包裹)。 - 优化方案:用
React.memo包裹编辑器父组件及相关子组件;确保useEditor配置参数稳定。
6. 大内容初始渲染开销
若初始加载的data?.content本身超3000字,Tiptap解析渲染时会消耗大量主线程资源,导致页面冻结。
- 优化方案:对大内容做懒加载,先渲染部分内容,剩余内容在编辑器初始化后异步插入;或用
editor.commands.insertContent分批插入内容。
内容的提问来源于stack exchange,提问作者Taufik Nurhidayat
相关产品推荐
相关产品推荐

