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就绪时机时,注册逻辑会失效。
优化方案
- 把和组件状态无关的常量、工具方法移到组件外部,避免重渲染重复创建
- 重构初始化时序,串行执行所有前置准备逻辑:先加载WASM -> 初始化onigLib -> 注册语言、拉取所有语法规则 -> 再渲染编辑器
- 移除
loadWASM的多余判断条件,确保WASM始终正常加载 - 增加加载状态,所有资源就绪前展示加载占位符,避免编辑器提前渲染
核心优化代码示例
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
相关产品推荐
相关产品推荐

