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

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>
    );
};

问题分析

  1. 装饰更新逻辑错误:每次调用editor.deltaDecorations([], [newDecoration])时,第一个参数为空数组,会清除所有现有装饰,只保留当前修改字符的装饰,导致之前的错误标记丢失,且无法覆盖全局所有错误字符。
  2. 仅处理单次修改:依赖change事件的单次修改范围,无法处理输入多行、删除字符等场景,导致错误标记不完整。
  3. 无效装饰冗余:添加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>
    );
};

关键修改点

  • 增量装饰更新:用decorationIds ref存储当前装饰ID,调用deltaDecorations时传入旧ID,Monaco会自动增量更新装饰,避免全量清除和重建,提升性能。
  • 全局字符对比:逐行逐字符对比输入文本与目标文本text1,覆盖所有不匹配场景(字符不等、输入行过长),确保错误标记准确完整。
  • 移除冗余逻辑:删除依赖change事件的局部处理逻辑,去掉无意义的bg-transparent装饰,简化代码同时减少性能开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 06:23:12