Monaco Editor React中使用deltaDecorations修改样式出错问题
Monaco Editor 错误字符高亮问题修复方案
在Monaco Editor中尝试为与特定变量text1不匹配的字符添加bg-red-600样式时,出现异常:只要存在一个错误字符,后续所有字符都会被错误高亮,即便它们与目标文本匹配。此前尝试的方案存在覆盖全部文本、性能低下的问题,以下是高效修复方案。
原问题代码
import Editor from '@monaco-editor/react'; import { getTheme } from '../models/StylesEditor'; import { useEditorContext } from '../context/EditorContext'; export const CodeEditor = () => { const text1 = `const num1 = 25; const num2 = 5; const result = num1 / num2; console.log(result)`; const textLines = text1.split('\n') const { config, themeEditor } = useEditorContext(); const eventKey = useRef(null); const monacoObj = useRef(null); const [value, setValue] = useState(''); const onValueChange = (newValue, event) => { eventKey.current = event; setValue(newValue); }; const handleEditorDidMount = (editor, monaco) => { monaco.editor.defineTheme('my-theme', getTheme(themeEditor)); monaco.editor.setTheme('my-theme'); monacoObj.current = { editor, monaco }; }; useEffect(() => { if (!monacoObj.current) return; const { editor, monaco } = monacoObj.current; const e = eventKey.current?.changes[0] const eR = e.range; const verification = textLines[eR.startLineNumber - 1][eR.startColumn - 1] !== e.text && e.text !== "" const classDeco = verification ? 'bg-red-600' : 'bg-transparent' const newDecoration = { range: new monaco.Range(eR.startLineNumber, eR.startColumn, eR.startLineNumber, eR.startColumn + 1), options: { inlineClassName: `${classDeco}` } } editor.deltaDecorations([], [newDecoration]) }, [value]); return ( <div className="h-full w-full flex items-center justify-center"> <Editor {...config} onMount={handleEditorDidMount} value={value} onChange={onValueChange} /> </div> ); };
问题分析
- 装饰更新逻辑错误:每次调用
editor.deltaDecorations([], [newDecoration])时,第一个参数为空数组,会清除所有现有装饰,只保留当前修改字符的装饰,导致之前的错误标记丢失,且无法覆盖全局所有错误字符。 - 仅处理单次修改:依赖change事件的单次修改范围,无法处理输入多行、删除字符等场景,导致错误标记不完整。
- 无效装饰冗余:添加
bg-transparent装饰无意义,增加不必要的性能开销。
修复方案
修改后的代码
import Editor from '@monaco-editor/react'; import { getTheme } from '../models/StylesEditor'; import { useEditorContext } from '../context/EditorContext'; import { useState, useRef, useEffect } from 'react'; export const CodeEditor = () => { const text1 = `const num1 = 25; const num2 = 5; const result = num1 / num2; console.log(result)`; const textLines = text1.split('\n'); const { config, themeEditor } = useEditorContext(); const monacoObj = useRef(null); const [value, setValue] = useState(''); const decorationIds = useRef([]); // 存储当前装饰ID,用于增量更新 const onValueChange = (newValue) => { setValue(newValue); }; const handleEditorDidMount = (editor, monaco) => { monaco.editor.defineTheme('my-theme', getTheme(themeEditor)); monaco.editor.setTheme('my-theme'); monacoObj.current = { editor, monaco }; }; useEffect(() => { if (!monacoObj.current || !value) return; const { editor, monaco } = monacoObj.current; const inputLines = value.split('\n'); const decorations = []; // 逐行逐字符对比,生成所有错误字符的装饰 inputLines.forEach((line, lineIndex) => { const targetLine = textLines[lineIndex]; if (!targetLine) return; // 对比相同长度部分的字符 const minLength = Math.min(line.length, targetLine.length); for (let colIndex = 0; colIndex < minLength; colIndex++) { if (line[colIndex] !== targetLine[colIndex]) { decorations.push({ range: new monaco.Range( lineIndex + 1, // Monaco行号从1开始 colIndex + 1, // Monaco列号从1开始 lineIndex + 1, colIndex + 2 ), options: { inlineClassName: 'bg-red-600' } }); } } // 标记输入行中超出目标行长度的多余字符 if (line.length > targetLine.length) { for (let colIndex = targetLine.length; colIndex < line.length; colIndex++) { decorations.push({ range: new monaco.Range( lineIndex + 1, colIndex + 1, lineIndex + 1, colIndex + 2 ), options: { inlineClassName: 'bg-red-600' } }); } } }); // 增量更新装饰:传入旧ID数组,Monaco自动处理新增/移除 decorationIds.current = editor.deltaDecorations(decorationIds.current, decorations); }, [value, textLines]); return ( <div className="h-full w-full flex items-center justify-center"> <Editor {...config} onMount={handleEditorDidMount} value={value} onChange={onValueChange} /> </div> ); };
关键修改点
- 增量装饰更新:用
decorationIdsref存储当前装饰ID,调用deltaDecorations时传入旧ID,Monaco会自动增量更新装饰,避免全量清除和重建,提升性能。 - 全局字符对比:逐行逐字符对比输入文本与目标文本
text1,覆盖所有不匹配场景(字符不等、输入行过长),确保错误标记准确完整。 - 移除冗余逻辑:删除依赖change事件的局部处理逻辑,去掉无意义的
bg-transparent装饰,简化代码同时减少性能开销。
内容的提问来源于stack exchange,提问作者Gael Idone
相关产品推荐
相关产品推荐

