当前位置: 首页 > article >正文

使用 CodeMirror 6 和 React 构建一个支持只读模式的 JSON 编辑器

       在现代 Web 开发中,代码编辑器是一个常见的需求,尤其是在需要编辑 JSON、JavaScript 或其他结构化数据的场景中。本文将介绍如何使用 CodeMirror 6 和 React 构建一个功能丰富的 JSON 编辑器,并支持通过开关切换只读模式。

1. 项目依赖

首先,我们需要安装以下依赖:

  • CodeMirror 6:一个功能强大的代码编辑器框架。

  • Ant Design:用于构建 UI 组件,这里我们使用 Switch 组件来切换只读模式。

  • React:用于构建用户界面。

 安装依赖的命令如下:

npm install @codemirror/commands @codemirror/lang-json @codemirror/lint @codemirror/state @codemirror/view antd react react-dom

2. 实现 JSON 编辑器

2.1 初始化编辑器

在组件挂载时,我们使用 useEffect 钩子初始化 CodeMirror 编辑器实例,并将其渲染到指定的 DOM 容器中。

useEffect(() => {
    if (containerRef.current) {
        const e = new EditorView({
            doc: code,
            extensions: extensionConfig,
            parent: containerRef.current,
        });
        setEditor(e);
    }
}, []);
2.2 编辑器扩展配置

编辑器的扩展配置包括行号显示、linting 结果边栏、默认键盘快捷键、JSON 语言支持等。

const extensionConfig = [
    lineNumbers(),     // 显示行号
    lintGutter(),      // 显示 linting 结果的边栏
    keymap.of(defaultKeymap),      // 使用默认键盘快捷键
    json(),            // 启用 JSON 语言支持 json()
];
2.3 自定义 JSON 解析 Linter

为了确保用户输入的 JSON 代码是有效的,我们实现了一个自定义的 JSON 解析 linter。如果文档为空,则不返回诊断信息;否则,使用 CodeMirror 提供的 jsonParseLinter 进行检查。

const jsonParseLinterWithCheck = () => {
    return (view: EditorView) => {
        const doc = view.state.doc.toString();
        if (doc.trim().length === 0) {
            return [];
        }
        const diagnostics = jsonParseLinter()(view);
        return diagnostics;
    };
};
2.4 编辑器更新监听器

我们为编辑器添加了一个更新监听器,用于在用户输入内容时更新 React 组件的状态。

const updateListener = EditorView.updateListener.of((vu) => {
    if (vu.docChanged) {
        const currentDoc = vu.state.doc.toString();
        setCode(currentDoc);
    }
});
2.5 只读模式切换

通过 Ant Design 的 Switch 组件,用户可以切换编辑器的只读模式。当 isReadonly 状态变化时,我们使用 StateEffect.reconfigure 动态更新编辑器的配置。

useEffect(() => {
    if (editor !== undefined && isReadonly !== undefined) {
        editor.dispatch({
            effects: StateEffect.reconfigure.of([
                ...extensionConfig,
                EditorState.readOnly.of(isReadonly),
            ]),
        });
    }
}, [isReadonly, editor]);

3. 完整代码

以下是完整的 React 组件代码:

import { defaultKeymap } from '@codemirror/commands';
import { json, jsonParseLinter } from '@codemirror/lang-json';
import { lintGutter, linter } from '@codemirror/lint';
import { EditorState, StateEffect } from '@codemirror/state';
import { EditorView, keymap, lineNumbers } from '@codemirror/view';
import { Switch } from 'antd';
import React, { useEffect, useRef, useState } from 'react';
// 自定义的 JSON 解析 linter,检查 JSON 代码的有效性
const jsonParseLinterWithCheck = () => {
    return (view: EditorView) => {
        // 如果文档为空,不返回诊断信息
        const doc = view.state.doc.toString();
        if (doc.trim().length === 0) {
            return [];
        }
        // 使用 JSON 解析 linter 进行诊断
        const diagnostics = jsonParseLinter()(view);
        return diagnostics;
    };
};
const TestDemo: React.FC = () => {
    const containerRef = useRef<HTMLDivElement>(null);
    const [editor, setEditor] = useState<EditorView>();
    const [code, setCode] = useState<string>('');
    const [isReadonly, setIsReadonly] = useState<boolean>(false);
    // 编辑器更新 监听器
    const updateListener = EditorView.updateListener.of((vu) => {
        if (vu.docChanged) {
            // if条件可添加 && !vu.transactions.some((tr) => tr.annotation(External))  排除远程变更
            // 如果if条件满足执行的代码(1)只用于更新本地状态(如显示当前内容),且没有其他副作用,一般不需要排除远程变更。
            //                       (2)会触发其他逻辑(如保存内容到服务器),则需要排除远程变更,避免重复触发。
            const currentDoc = vu.state.doc.toString();
            setCode(currentDoc);
        }
    });
    // 编辑器的扩展配置
    // (1)显示行号 lineNumbers()
    // (2)显示 linting 结果的边栏  lintGutter()
    // (3)使用默认键盘快捷键 keymap.of(defaultKeymap)
    // (4)启用 JSON 语言支持 json()
    // (5)启用自定义的 linter  linter(jsonParseLinterWithCheck())
    const extensionConfig = [lineNumbers(), lintGutter(), keymap.of(defaultKeymap), json(), linter(jsonParseLinterWithCheck()), updateListener];
    useEffect(() => {
        if (containerRef.current) {
            // 创建编辑器实例
            const e = new EditorView({
                doc: code,
                extensions: extensionConfig,
                parent: containerRef.current,
            });
            setEditor(e);
        }
    }, []);
    // 当isReadonly状态或编辑器实例变化时重置编辑器配置(只读状态)
    useEffect(() => {
        if (editor !== undefined && isReadonly !== undefined) {
            editor.dispatch({
                effects: StateEffect.reconfigure.of([...extensionConfig, EditorState.readOnly.of(isReadonly)]),
            });
        }
    }, [isReadonly, editor]);
    return (
        <>
            <Switch
                style={{ margin: '0 0 15px 20px' }}
                checkedChildren="只读"
                unCheckedChildren="编辑"
                onChange={(value) => {
                    setIsReadonly(value);
                }}
            />
            {/* 编辑器渲染的容器 */}
            <div ref={containerRef} />
        </>
    );
};

export default TestDemo;

4. 运行效果

  • 用户可以在编辑器中输入 JSON 代码,编辑器会实时检查 JSON 的有效性。

  • 点击 Switch 组件可以切换编辑器的只读模式,在只读模式下,用户无法编辑内容。


http://www.kler.cn/a/570501.html

相关文章:

  • 基于QSSH开源库实现SSH远程连接和SFTP文件传输
  • DeepSeek集成到VScode工具,让编程更高效
  • 玩转python: 几个案例-掌握贪心算法
  • 基于AT89C52单片机的停车场车位管理系统
  • VsCode + EIDE + OpenOCD + STM32(野火DAP) 开发环境配置
  • doris:阿里云 DLF
  • PyTorch 中使用多进程实现增量训练
  • 使用cursor ai 开发 UniApp JSON 工具开发文档
  • 第十四届蓝桥杯:(二分算法)字串简写
  • 【MySQL】CAST()在MySQL中的用法以及其他常用的数据类型转换函数
  • 【部署】Docker Compose 指令备忘清单(超级详细!)
  • docker拉取乌班图并且ssh连接
  • C++小课堂——变量的声明,赋值和初始化
  • Redis是什么?如何使用Redis进行缓存操作?
  • Powershell和BTEQ工具实现带多组参数和标签的Teradata数据库批量数据导出程序
  • 深度学习-13.深度强化学习:深度 Q 学习
  • 【网络编程】之TCP通信步骤
  • 基础篇——深入解析SQL多表操作与关联查询:构建复杂数据关系的桥梁
  • 《解锁HarmonyOS NEXT高阶玩法:艺术图像识别功能开发全攻略》
  • Nginx将tomcat项目转发。将非80/443端口口转为80或443及https