Skip to content
返回设计中心

代码编辑器组件(Monaco/CodeMirror)

嵌入带语言模式、lint、懒加载控包大小的代码编辑器。

组件模式高级

为什么

textarea 适合单行输入但对代码无用:无语法高亮、无缩进、无 lint。Monaco 或 CodeMirror 给 IDE 级体验,但重 — 懒加载强制。

复合组件模式被 Radix UI、Headless UI 和 shadcn/ui 采用 — 这是 2024 年采用最多的三个无头库。相比扁平组件 API,它们减少 70% 的属性逐层传递。

何时使用

用于配置编辑(JSON/YAML)、查询输入(SQL/GraphQL)或浏览器内 IDE 功能。纯文本用 textarea 更轻。

如何做

1. 选 — Monaco(VS Code 引擎,重约 2MB)或 CodeMirror 6(模块化,约 200KB) 2. 懒加载 — React.lazy + 动态 import,加载前显示 textarea 占位 3. 语言 — 仅注册所需模式(json、sql),非整语言包 4. lint — 接 LanguageServer 或简单 linter 函数到 editor.onDidChange 5. 主题 — 匹配应用主题;Monaco 主题是 JSON,CodeMirror 用 @codemirror/theme-one-dark 6. 值同步 — 受控模式棘手;外部变时用 editor.setValue,防循环 7. worker — Monaco 用 web worker 做语言服务;配置 MonacoEnvironment.getWorkerUrl

示例

import { lazy, Suspense, useState } from "react"; // 仅编辑器挂载时懒加载 Monaco const MonacoEditor = lazy(() => import("@monaco-editor/react").then((m) => ({ default: m.default }))); function CodeEditor({ value, onChange, language = "json" }: Props) { const [loaded, setLoaded] = useState(false); return ( <Suspense fallback={<textarea value={value} onChange={(e) => onChange(e.target.value)} className="w-full h-64 font-mono" />}> <MonacoEditor value={value} language={language} theme="vs-dark" onChange={(v) => onChange(v ?? "")} onMount={() => setLoaded(true)} options={{ minimap: { enabled: false }, fontSize: 13, lineNumbers: "on" }} loading={<div className="grid place-items-center h-64">加载编辑器…</div>} /> </Suspense> ); }

常见问题

  • ⚠Monaco 在主包 — 2MB 发给每个用户即使从不开编辑器
  • ⚠worker URL 配错 — 语言服务静默失败,无自动补全或诊断
  • ⚠受控值循环 — onChange 设状态、setValue 触发 onChange、光标跳

最佳实践

  • ✓懒加载编辑器 — 首次用动态 import,textarea 回退保持页面轻
  • ✓仅注册所需语言 — Monaco 全语言包巨大,仅 import json/sql
  • ✓用非受控模型 — 让编辑器拥有值,提交时读,避免 setValue 循环

常见问题解答