使用 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
组件可以切换编辑器的只读模式,在只读模式下,用户无法编辑内容。