如何使用 @codemirror/view 库创建一个简单的代码编辑器

CodeMirror6 是状态与视图分离架构:

  • @codemirror/state:纯数据层,EditorState(文档、选区、不可变),无 DOM
  • @codemirror/view :视图渲染层,把 EditorState 渲染成 DOM、处理输入、滚动、光标、装饰、交互

1. 安装依赖

perl 复制代码
npm install @codemirror/view @codemirror/state

最小核心只需要这两个包,@codemirror/view 视图渲染,@codemirror/state 状态管理。 如果想要行号、快捷键、选中样式,可以额外引入内置扩展。

2. 完整最小示例(原生HTML+TS/JS)

index.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>CM6 Demo</title>
  <style>
    #editor {
      height: 400px;
      border: 1px solid #ccc;
    }
  </style>
</head>
<body>
  <div id="editor"></div>
  <script type="module" src="./main.js"></script>
</body>
</html>

main.js

js 复制代码
import { EditorView, lineNumbers, highlightActiveLine, drawSelection, EditorView } from "@codemirror/view"
import { EditorState } from "@codemirror/state"

// 1. 创建 state(纯数据层,无DOM)
const state = EditorState.create({
  doc: `// 测试代码
function hello() {
  return "Hello CodeMirror View"
}
`,
  extensions: [
    lineNumbers(),          // 行号
    highlightActiveLine(),  // 高亮当前行
    drawSelection(),        // 绘制选区光标
    // 监听内容变化
    EditorView.updateListener.of((update) => {
      if (update.docChanged) {
        // 获取最新文本
        const text = update.state.doc.toString()
        console.log("编辑器内容:", text)
      }
    })
  ]
})

// 2. 创建 EditorView,挂载到DOM
const view = new EditorView({
  state,
  parent: document.querySelector("#editor")
})

// ========== 常用API ==========
// 获取全部文本
console.log(view.state.doc.toString())

// 编程方式修改内容(必须 dispatch,禁止直接改dom)
// view.dispatch({
//   changes: { from: 0, to: view.state.doc.length, insert: "新内容" }
// })

// 销毁编辑器(组件卸载时调用,防止内存泄漏)
// view.destroy()

3. 核心流程

EditorState.create → 生成不可变状态(文档、选区、扩展) new EditorView({state, parent}) → 视图订阅state,渲染DOM、处理输入事件 ✅ 单向数据流:所有修改必须走 view.dispatch(transaction)

4. 可选:加上JS语法高亮

bash 复制代码
npm install @codemirror/lang-javascript
css 复制代码
import { javascript } from "@codemirror/lang-javascript"

const state = EditorState.create({
  doc: "...",
  extensions: [
    lineNumbers(),
    highlightActiveLine(),
    drawSelection(),
    javascript() // JS语法高亮
  ]
})

5. React简易封装版本

javascript 复制代码
import { useEffect, useRef } from "react"
import { EditorView, lineNumbers, highlightActiveLine, drawSelection, EditorView } from "@codemirror/view"
import { EditorState } from "@codemirror/state"

export default function SimpleCodeEditor() {
  const containerRef = useRef<HTMLDivElement>(null)
  const viewRef = useRef<EditorView | null>(null)

  useEffect(() => {
    if (!containerRef.current) return
    const state = EditorState.create({
      doc: "const a = 1",
      extensions: [
        lineNumbers(),
        highlightActiveLine(),
        drawSelection(),
        EditorView.updateListener.of(u => {
          if(u.docChanged) console.log(u.state.doc.toString())
        })
      ]
    })
    const view = new EditorView({ state, parent: containerRef.current })
    viewRef.current = view

    return () => {
      view.destroy() // 卸载销毁!
    }
  }, [])

  return <div ref={containerRef} style={{height:400, border:"1px solid #ddd"}}/>
}
相关推荐
FungLeo12 小时前
成为全栈·Vue 管理后台篇·Vue Router如何守住多角色后台边界
前端·javascript·vue.js·rbac·vue router·路由守卫·前端权限
FungLeo12 小时前
成为全栈·Vue 管理后台篇·分页只显示第一页:从接口total 定位到前端修复
前端·vue.js·状态模式·分页·naive ui·服务端分页·前端问题排查
ZhengEnCi12 小时前
苹果滑动为什么有「肉感」:橡皮筋越界阻尼与弹簧动画的物理原理
前端
Moment13 小时前
不会后期的我,发现了一个开源胶片神器😍😍😍
前端·后端·开源
L1mbo13 小时前
Lite模式 缓解微信开发者工具卡顿,附 uni-app 实践方案
前端·微信小程序
FungLeo13 小时前
成为全栈·Vue管理后台篇·仪表盘不是几张卡片:组合统计接口与角色视图
前端·javascript·vue.js·rbac·仪表盘·管理后台·tanstack query
SameX13 小时前
HarmonyOS 做桌面卡片 onAddForm 要同步返回但数据在数据库里的解法
前端
deli00713 小时前
手机夜景模式为什么要举 3 秒?8 帧 RAW 堆栈 SNR 实测
前端
松仔log13 小时前
Kotlin中级——callbackFlow
java·前端·kotlin
三小河13 小时前
SWC 凭什么比 Babel 快 ?项目优化迁移踩坑记录
前端·后端