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

Next.js中使用react-monaco-editor语法高亮偶发失效问题求助

问题根因分析

语法高亮偶发失效是初始化时序竞态和重复注册冲突共同导致的,具体问题点如下:

  • 依赖加载时序错误:vscode-oniguruma的WASM文件是语法高亮的核心依赖,你将loadWASM的执行限制在props.openFiles.length === 1的条件下,且未等待WASM加载完成就初始化onigLib、注册语言、挂载编辑器,WASM加载速度随机,加载完成前调用语法解析API会静默失败,导致高亮失效。
  • 资源加载无等待逻辑:语法规则文件、语言配置文件的拉取都是异步操作,你未等待这些资源加载完成就渲染编辑器,资源加载慢时编辑器渲染完成后无可用语法规则,自然没有高亮。
  • 重复注册冲突:你将languages、grammars、provider等全局配置都定义在组件内部,组件每次重渲染都会重新创建实例、重复向monaco注册语言,多次注册会导致monaco内部的语言服务逻辑冲突。
  • 动态导入时序冲突:react-monaco-editor是动态导入的,你未等待monaco实例完全加载完成就执行注册逻辑,注册时机早于monaco就绪时机时,注册逻辑会失效。
优化方案
  1. 把和组件状态无关的常量、工具方法移到组件外部,避免重渲染重复创建
  2. 重构初始化时序,串行执行所有前置准备逻辑:先加载WASM -> 初始化onigLib -> 注册语言、拉取所有语法规则 -> 再渲染编辑器
  3. 移除loadWASM的多余判断条件,确保WASM始终正常加载
  4. 增加加载状态,所有资源就绪前展示加载占位符,避免编辑器提前渲染

核心优化代码示例

import React, { useState, useEffect, useRef } from "react";
import dynamic from "next/dynamic";

import { setformatter } from "../actions/getdataaction";
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
import {
  createOnigScanner,
  createOnigString,
  loadWASM,
} from "vscode-oniguruma";
import { SimpleLanguageInfoProvider } from "./providers";
import { registerLanguages } from "./register";
import { rehydrateRegexps } from "./configuration";
import VsCodeDarkTheme from "./vs-dark-plus-theme";
import { html, scss, css, javascript, typescript, python } from "./samplecode";

const MonacoEditor = dynamic(import("react-monaco-editor"), { ssr: false });

// 常量移到组件外部
const languages = [
  {
    id: "python",
    extensions: [".py"],
    aliases: ["Python", "py"],
    filenames: ["Snakefile", "BUILD", "BUCK", "TARGETS"],
    firstLine: "^#!\\s*/?.*\\bpython[0-9.-]*\\b",
  },
  {
    id: "javascript",
    extensions: [".js", ".jsx"],
    aliases: ["Javascript", "JS", "js"],
    filenames: ["Snakefile", "BUILD", "BUCK", "TARGETS"],
  },
  {
    id: "css",
    extensions: [".css"],
    aliases: ["CSS", "css"],
    filenames: ["Snakefile", "BUILD", "BUCK", "TARGETS"],
  },
  {
    id: "scss",
    extensions: [".scss", ".sass"],
    aliases: ["SCSS", "scss", "sass"],
    filenames: ["Snakefile", "BUILD", "BUCK", "TARGETS"],
  },
  {
    id: "html",
    extensions: [".html"],
    aliases: ["HTML", "html"],
    filenames: ["Snakefile", "BUILD", "BUCK", "TARGETS"],
  },
  {
    id: "typescript",
    extensions: [".ts", ".tsx"],
    aliases: ["Typescript", "TS", "ts"],
    filenames: ["Snakefile", "BUILD", "BUCK", "TARGETS"],
  },
  {
    id: "json",
    extensions: [".json"],
    aliases: ["JSON", "json"],
    filenames: ["Snakefile", "BUILD", "BUCK", "TARGETS"],
  },
];
const grammars = {
  "source.py": {
    language: "python",
    path: "python.tmLanguage.json",
  },
  "source.js": {
    language: "javascript",
    path: "javascript.tmLanguage.json",
  },
  "source.css": {
    language: "css",
    path: "css.tmLanguage.json",
  },
  "source.css.scss": {
    language: "scss",
    path: "scss.tmLanguage.json",
  },
  "text.html.basic": {
    language: "html",
    path: "html.tmLanguage.json",
  },
  "source.ts": {
    language: "typescript",
    path: "typescript.tmLanguage.json",
  },
  "source.json": {
    language: "json",
    path: "json.tmLanguage.json",
  },
};

const fetchGrammar = async (scopeName) => {
  const { path } = grammars[scopeName];
  const uri = `grammars/${path}`;
  const response = await fetch(uri);
  const grammar = await response.text();
  const type = path.endsWith(".json") ? "json" : "plist";
  return { type, grammar };
};

const fetchConfiguration = async (language) => {
  const uri = `configurations/${language}.json`;
  const response = await fetch(uri);
  const rawConfiguration = await response.text();
  return rehydrateRegexps(rawConfiguration);
};

async function loadVSCodeOnigurumWASM() {
  const response = await fetch("release/onig.wasm");
  const contentType = response.headers.get("content-type");
  if (contentType === "application/wasm") {
    return response;
  }
  return await response.arrayBuffer();
}

let changescontent = [];
let cursorposition = 1;
let isInited = false; // 标记是否已经初始化过,避免重复注册

function Editor(props) {
  var language = props.extension;

  const editorRef = useRef(null);
  const monacoRef = useRef(null);
  const [inited, setInited] = useState(false);
  var code =
    language === "html"
      ? html
      : language === "css"
      ? css
      : language === "scss"
      ? scss
      : language === "javascript"
      ? javascript
      : language === "typescript"
      ? typescript
      : language === "python"
      ? python
      : "";
  const [editorContent, setEditorContent] = useState(code);
  
  useEffect(() => {
    if(isInited) return;
    main();
  }, []);

  async function main() {
    // 先加载WASM
    const data = await loadVSCodeOnigurumWASM();
    await loadWASM(data);
    // 初始化onigLib
    const onigLib = Promise.resolve({
      createOnigScanner,
      createOnigString,
    });
    // 初始化provider
    const provider = new SimpleLanguageInfoProvider({
      grammars,
      fetchGrammar,
      configurations: languages.map((language) => language.id),
      fetchConfiguration,
      theme: VsCodeDarkTheme,
      onigLib,
      monaco,
    });
    // 注册语言,等待所有语言信息加载完成
    await registerLanguages(
      languages,
      (language) => provider.fetchLanguageInfo(language),
      monaco
    );
    provider.injectCSS();
    // 标记初始化完成
    isInited = true;
    setInited(true);
  }

  if(!inited) return <div style={{width: '100%', height: '560px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff'}}>加载中...</div>

  return (
    <>
      <div style={{ width: "100%", height: "100%" }}>
        <MonacoEditor
          editorDidMount={(editor, monaco) => {
            editorRef.current = editor;
            monacoRef.current = monaco;
            // 主动触发重着色
            editor.trigger('init', 'editor.action.colorize');
          }}
          width="100%"
          height="560"
          language={language}
          theme="vs-dark"
          value={editorContent}
          options={{
            minimap: {
              enabled: false,
            },
          }}
        />
      </div>
    </>
  );
}
export default Editor;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 14:06:02