Next.js中ProseMirror最简配置及变量变化时高亮单词问题
ProseMirror在Next.js中的重复渲染与动态单词高亮问题
我在Next.js项目中搭建简易ProseMirror编辑器,代码如下:
import { useEffect } from "react"; 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"; const Editor = () => { useEffect(() => { const mySchema = new Schema({ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"), marks: schema.spec.marks, }); new EditorView(document.querySelector("#editor"), { state: EditorState.create({ doc: DOMParser.fromSchema(mySchema).parse( document.querySelector("#content") ), plugins: exampleSetup({ schema: mySchema }), }), }); }, []); return ( <> <div id="editor"></div> <div id="content"></div> </> ); }; export default Editor;
目前遇到编辑器重复渲染的问题,核心需求是:当存储待高亮单词索引的变量发生变化时,自动更新编辑器内容并高亮对应的单词。试过Draft.js也没解决,求技术提示。
解决方案
一、解决重复渲染问题
当前代码会在组件重渲染时(比如Next.js的Hydration、父组件更新触发)重复创建EditorView实例,导致编辑器重复渲染。通过useRef保存实例并在组件卸载时销毁,可避免该问题:
import { useEffect, useRef } from "react"; // 保留原有导入 const Editor = () => { const editorViewRef = useRef(null); const schemaRef = useRef(null); useEffect(() => { // 仅初始化一次Schema schemaRef.current = new Schema({ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"), marks: { ...schema.spec.marks, // 新增高亮标记类型,用于后续单词高亮 highlight: { attrs: { color: { default: "#ffeb3b" } }, parseDOM: [{ style: "background-color" }], toDOM: (mark) => ["span", { style: `background-color: ${mark.attrs.color}` }, 0] } }, }); // 仅在视图未初始化时创建EditorView if (!editorViewRef.current) { const initialDoc = DOMParser.fromSchema(schemaRef.current).parse( document.querySelector("#content") ); editorViewRef.current = new EditorView(document.querySelector("#editor"), { state: EditorState.create({ doc: initialDoc, plugins: exampleSetup({ schema: schemaRef.current }), }), }); } // 组件卸载时销毁编辑器,避免内存泄漏 return () => { if (editorViewRef.current) { editorViewRef.current.destroy(); editorViewRef.current = null; } }; }, []); return ( <> <div id="editor"></div> {/* 初始化内容容器可隐藏,避免干扰页面 */} <div id="content" style={{ display: "none" }}></div> </> ); };
二、实现动态单词高亮
通过监听高亮索引变量变化,操作ProseMirror事务(Transaction)更新文档标记,实现动态高亮:
- 接收高亮索引作为组件props,并监听其变化:
const Editor = ({ highlightWordIndexes }) => { // 保留之前的ref和初始化代码 useEffect(() => { if (!editorViewRef.current || !highlightWordIndexes) return; const { state, dispatch } = editorViewRef.current; let tr = state.tr; // 先清除所有已存在的高亮标记 state.doc.descendants((node, pos) => { if (node.isText) { tr = tr.removeMark(pos, pos + node.nodeSize, schemaRef.current.marks.highlight); } }); // 遍历文档,匹配目标索引的单词并添加高亮 let currentWordIndex = 0; state.doc.descendants((node, pos) => { if (node.isText) { // 按空格拆分单词,可根据需求调整拆分规则(如处理标点) const words = node.text.split(/\s+/); let textOffset = 0; words.forEach(word => { if (highlightWordIndexes.includes(currentWordIndex)) { const mark = schemaRef.current.marks.highlight.create(); const startPos = pos + textOffset; const endPos = startPos + word.length; tr = tr.addMark(startPos, endPos, mark); } textOffset += word.length + 1; // 加上空格的长度 currentWordIndex++; }); } }); // 仅在文档有变更时提交事务 if (tr.docChanged) { dispatch(tr); } }, [highlightWordIndexes]); // 保留原有返回JSX };
- 关键注意点:
- 单词拆分逻辑可根据实际文本格式调整(比如处理带标点的单词)
- 传入的
highlightWordIndexes建议用useMemo包裹,避免因数组引用频繁变化触发不必要的高亮更新 - 若初始化内容不需要展示,务必隐藏
#content容器
内容的提问来源于stack exchange,提问作者Merhawi Fissehaye
相关产品推荐
相关产品推荐

