You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)更新文档标记,实现动态高亮:

  1. 接收高亮索引作为组件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
};
  1. 关键注意点:
  • 单词拆分逻辑可根据实际文本格式调整(比如处理带标点的单词)
  • 传入的highlightWordIndexes建议用useMemo包裹,避免因数组引用频繁变化触发不必要的高亮更新
  • 若初始化内容不需要展示,务必隐藏#content容器

内容的提问来源于stack exchange,提问作者Merhawi Fissehaye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 22:45:32