Monaco Editor for React - use the monaco-editor in any React application without needing to use webpack (or rollup/parcel/etc) configuration files / plugins
@monaco-editor/react ·

Monaco Editor for React · 在任何 React 应用中使用 monaco-editor,无需配置 webpack(或 rollup/parcel 等)配置文件或插件
- ✅ 支持 React
V19! - ⌨️ 使用
TypeScript重写 🔥 - ⚡ 多模型编辑器 已支持,尽情使用吧 🎉
- 🎉
v4版本已发布 - 如需了解新版本的更新内容以及如何从v3迁移,请阅读此文档(另外,若需旧版本README,可查看此处) - 🎮 新增开发/ playground 章节 - 现在您可以运行 playground 并深入了解库的内部实现
- 💫 已集成 @monaco-editor/loader
简介
Monaco 编辑器包装器,可轻松地与任何 React 应用进行一键式集成,无需使用 webpack(或任何其他模块打包器)的配置文件或插件。它可用于由 create-react-app、create-snowpack-app、vite、Next.js 或任何其他应用生成器创建的应用 - 您无需 eject 或重新配置它们。
动机
monaco-editor 是一款基于 Web 技术的知名代码编辑器,为 VS Code 提供支持。本库负责 monaco-editor 的设置流程,并提供简洁的 API,以便在任何 React 环境中与 monaco 进行交互。
演示
文档
安装
npm install @monaco-editor/react # or @monaco-editor/react@next for React v19
或
yarn add @monaco-editor/react
或者你可以使用 CDN。这里有一个示例
注意:对于 TypeScript 类型定义,此包将 monaco-editor 包用作对等依赖项。因此,如果你需要类型定义且尚未安装 monaco-editor 包,则需要进行安装。
咨询 AI
Monaco-React AI 将帮助你更好地理解此仓库。你可以询问代码示例、安装指南、调试帮助等更多内容。
简介
除了类型之外,该库还导出 Editor 和 DiffEditor 组件,以及 loader 工具和 useMonaco 钩子:
import Editor, { DiffEditor, useMonaco, loader } from '@monaco-editor/react';
使用方法
简单使用
以下是 monaco 编辑器与 React 项目简单集成的示例。
您只需导入并渲染 Editor 组件即可:
import React from 'react';
import ReactDOM from 'react-dom';
import Editor from '@monaco-editor/react';
function App() {
return <Editor height="90vh" defaultLanguage="javascript" defaultValue="// some comment" />;
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
扩展示例
import React from 'react';
import ReactDOM from 'react-dom';
import Editor from '@monaco-editor/react';
function App() {
function handleEditorChange(value, event) {
// here is the current value
}
function handleEditorDidMount(editor, monaco) {
console.log('onMount: the editor instance:', editor);
console.log('onMount: the monaco instance:', monaco);
}
function handleEditorWillMount(monaco) {
console.log('beforeMount: the monaco instance:', monaco);
}
function handleEditorValidation(markers) {
// model markers
// markers.forEach(marker => console.log('onValidate:', marker.message));
}
return (
<Editor
height="90vh"
defaultLanguage="javascript"
defaultValue="// some comment"
onChange={handleEditorChange}
onMount={handleEditorDidMount}
beforeMount={handleEditorWillMount}
onValidate={handleEditorValidation}
/>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
获取值
获取当前值有两种方式:
- 从
editor实例中获取当前模型的值
import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import Editor from '@monaco-editor/react';
function App() {
const editorRef = useRef(null);
function handleEditorDidMount(editor, monaco) {
editorRef.current = editor;
}
function showValue() {
alert(editorRef.current.getValue());
}
return (
<>
<button onClick={showValue}>Show value</button>
<Editor
height="90vh"
defaultLanguage="javascript"
defaultValue="// some comment"
onMount={handleEditorDidMount}
/>
</>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
- 通过
onChange属性获取当前模型的值
import React from 'react';
import ReactDOM from 'react-dom';
import Editor from '@monaco-editor/react';
function App() {
function handleEditorChange(value, event) {
console.log('here is the current model value:', value);
}
return (
<Editor
height="90vh"
defaultLanguage="javascript"
defaultValue="// some comment"
onChange={handleEditorChange}
/>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
(通过 `editor` 实例获取 `DiffEditor` 的值)
import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import { DiffEditor } from '@monaco-editor/react';
function App() {
const diffEditorRef = useRef(null);
function handleEditorDidMount(editor, monaco) {
diffEditorRef.current = editor;
}
function showOriginalValue() {
alert(diffEditorRef.current.getOriginalEditor().getValue());
}
function showModifiedValue() {
alert(diffEditorRef.current.getModifiedEditor().getValue());
}
return (
<>
<button onClick={showOriginalValue}>show original value</button>
<button onClick={showModifiedValue}>show modified value</button>
<DiffEditor
height="90vh"
language="javascript"
original="// the original code"
modified="// the modified code"
onMount={handleEditorDidMount}
/>
</>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
editor instance
editor 实例通过 onMount 属性的第一个参数暴露,第二个参数是 monaco 实例
import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import Editor from '@monaco-editor/react';
function App() {
const editorRef = useRef(null);
function handleEditorDidMount(editor, monaco) {
// here is the editor instance
// you can store it in `useRef` for further usage
editorRef.current = editor;
}
return (
<Editor
height="90vh"
defaultLanguage="javascript"
defaultValue="// some comment"
onMount={handleEditorDidMount}
/>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
monaco instance
获取 monaco 实例有三种方式:
- 通过
onMount/beforeMount
import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import Editor from '@monaco-editor/react';
function App() {
const monacoRef = useRef(null);
function handleEditorWillMount(monaco) {
// here is the monaco instance
// do something before editor is mounted
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
}
function handleEditorDidMount(editor, monaco) {
// here is another way to get monaco instance
// you can also store it in `useRef` for further usage
monacoRef.current = monaco;
}
return (
<Editor
height="90vh"
defaultLanguage="javascript"
defaultValue="// some comment"
beforeMount={handleEditorWillMount}
onMount={handleEditorDidMount}
/>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
- 通过
loader工具
import { loader } from '@monaco-editor/react';
loader.init().then((monaco) => console.log('here is the monaco instance:', monaco));
- 通过
useMonaco钩子
import React from 'react';
import ReactDOM from 'react-dom';
import Editor, { useMonaco } from '@monaco-editor/react';
function App() {
const monaco = useMonaco();
useEffect(() => {
if (monaco) {
console.log('here is the monaco instance:', monaco);
}
}, [monaco]);
return <Editor height="90vh" defaultValue="// some comment" defaultLanguage="javascript" />;
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
useMonaco
useMonaco 是一个 React 钩子,返回 monaco 的实例。但有一个重要注意事项需要考虑:初始化过程由 loader 工具(@monaco-editor/loader 的引用)处理:该过程是异步且仅执行一次的。因此,如果初始化的第一个发起者是 useMonaco 钩子,由于其异步加载特性,首次返回值将为 null。只需检查 useMonaco 的返回值即可。
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import Editor, { useMonaco } from '@monaco-editor/react';
function App() {
const monaco = useMonaco();
useEffect(() => {
// do conditional chaining
monaco?.languages.typescript.javascriptDefaults.setEagerModelSync(true);
// or make sure that it exists by other ways
if (monaco) {
console.log('here is the monaco instance:', monaco);
}
}, [monaco]);
return <Editor height="90vh" defaultValue="// some comment" defaultLanguage="javascript" />;
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
loader-config
该库导出(命名导出)了名为 loader 的工具。本质上,它是 @monaco-editor/loader 的引用。默认情况下,monaco 文件会从 CDN 下载。您可以更改此行为以及其他与 monaco 的 AMD 加载器相关的设置。我们提供了一个默认的 配置文件,您可以通过以下方式对其进行修改:
import { loader } from '@monaco-editor/react';
// you can change the source of the monaco files
loader.config({ paths: { vs: '...' } });
// you can configure the locales
loader.config({ 'vs/nls': { availableLanguages: { '*': 'de' } } });
// or
loader.config({
paths: {
vs: '...',
},
'vs/nls': {
availableLanguages: {
'*': 'de',
},
},
});
将 monaco-editor 用作 npm 包
从 v4.4.0 版本开始,可以将 monaco-editor 用作 npm 包;从 node_modules 中导入它,并将 monaco 源代码包含到你的 bundle 中(而非使用 CDN)。要实现此功能,你可以按以下步骤操作:
import * as monaco from 'monaco-editor';
import { loader } from '@monaco-editor/react';
loader.config({ monaco });
// ...
注意:您需要知道,这可能需要额外的 webpack 插件,例如 monaco-editor-webpack-plugin,或者在不弹出 CRA 生成的应用的情况下,可能无法使用它。
如果您使用 Vite,则需要执行以下操作:
import { loader } from '@monaco-editor/react';
import * as monaco from 'monaco-editor';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new jsonWorker();
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new cssWorker();
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new htmlWorker();
}
if (label === 'typescript' || label === 'javascript') {
return new tsWorker();
}
return new editorWorker();
},
};
loader.config({ monaco });
loader.init().then(/* ... */);
注意:您传入的对象将与默认对象进行深度合并。
多模型编辑器
当您渲染 Editor 组件时,会创建一个默认模型。需要重点说明的是,当您更改 language 或 value 属性时,它们会影响组件挂载时自动创建的同一个模型。在大多数情况下这没问题,但当开发者想要实现多模型编辑器以支持类似 IDE 中的标签/文件功能时,就会遇到问题。以前,要处理多个模型,他们必须在组件外部手动操作。现在,多模型 API 已得到支持 🎉 让我们看看它是如何工作的。创建模型有三个参数:value、language 和 path(monaco.editor.createModel(value, language, monaco.Uri.parse(path)))。您可以将最后一个参数(path)视为模型的标识符。Editor 组件现在有一个 path 属性。当您指定 path 属性时,Editor 组件会检查是否存在具有该路径的模型。如果存在,将显示现有模型;否则,将创建(并存储)一个新模型。使用这种技术,您可以将文件与路径关联起来,创建一个完整的多模型编辑器。您可以打开文件、进行一些更改、选择另一个文件,当您回到第一个文件时,将显示之前的模型及其整个视图状态、文本选择、撤销堆栈、滚动位置等。(简单演示)
下面是一个简单示例:假设我们有一个类似 JSON 的文件结构表示,大致如下:
const files = {
'script.js': {
name: 'script.js',
language: 'javascript',
value: someJSCodeExample,
},
'style.css': {
name: 'style.css',
language: 'css',
value: someCSSCodeExample,
},
'index.html': {
name: 'index.html',
language: 'html',
value: someHTMLCodeExample,
},
};
以下是我们简单的多模型编辑器实现:
import React from 'react';
import ReactDOM from 'react-dom';
import Editor from '@monaco-editor/react';
function App() {
const [fileName, setFileName] = useState('script.js');
const file = files[fileName];
return (
<>
<button disabled={fileName === 'script.js'} onClick={() => setFileName('script.js')}>
script.js
</button>
<button disabled={fileName === 'style.css'} onClick={() => setFileName('style.css')}>
style.css
</button>
<button disabled={fileName === 'index.html'} onClick={() => setFileName('index.html')}>
index.html
</button>
<Editor
height="80vh"
theme="vs-dark"
path={file.name}
defaultLanguage={file.language}
defaultValue={file.value}
/>
</>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
属性:
defaultValuedefaultLanguagedefaultPathvaluelanguagepathsaveViewState
这些属性将为您在使用多模型编辑器时提供更大的灵活性。
注意
defaultValue、defaultLanguage 和 defaultPath 仅在创建新模型期间被考虑
value、language 和 path 始终被跟踪
saveViewState 是一个指示器,用于决定是否在模型切换时保存模型的视图状态
onValidate
onValidate 是一个附加属性。当当前模型的内容发生更改且当前模型标记准备就绪时,会触发一个事件。触发时将携带当前模型的标记信息。
import React from 'react';
import ReactDOM from 'react-dom';
import Editor from '@monaco-editor/react';
function App() {
function handleEditorValidation(markers) {
// model markers
markers.forEach((marker) => console.log('onValidate:', marker.message));
}
return (
<Editor
height="90vh"
defaultLanguage="javascript"
defaultValue="// let's write some broken code 😈"
onValidate={handleEditorValidation}
/>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
需要说明的是,根据monaco-editor的划分,所有支持的语言分为两类:
- 具备丰富
IntelliSense和验证功能的语言
TypeScriptJavaScriptCSSLESSSCSSJSONHTML
- 仅支持基本语法着色的语言
XMLPHPC#C++RazorMarkdownDiffJavaVBCoffeeScriptHandlebarsBatchPugF#LuaPowershellPythonRubySASSRObjective-C
正如您所料,onValidate属性仅对第一组语言生效
注意事项
针对electron用户
作为一个常规的React组件,它在electron-react环境中也能正常工作,无需额外的webpack配置或其他附加操作。但开发者在使用过程中通常会遇到一些可能令人困惑的情况,具体如下:
- 加载界面卡住
通常是因为您的环境不允许加载外部资源。默认情况下,组件会从
CDN加载monaco资源。您可以查看默认配置。不过,您完全可以更改此行为;该库是可完全配置的。相关内容请阅读此处。因此,如果您希望从本地文件加载,可以按以下方式操作:
import { loader } from '@monaco-editor/react';
loader.config({ paths: { vs: '../path-to-monaco' } });
- 根据你的 electron 环境,可能需要使用绝对 URL
从 这里 获取的工具函数可以帮助你实现这一点。假设你已安装
monaco-editor包,并且希望从node_modules而非 CDN 加载 monaco:在这种情况下,你可以编写如下代码:
function ensureFirstBackSlash(str) {
return str.length > 0 && str.charAt(0) !== '/' ? '/' + str : str;
}
function uriFromPath(_path) {
const pathName = path.resolve(_path).replace(/\\/g, '/');
return encodeURI('file://' + ensureFirstBackSlash(pathName));
}
loader.config({
paths: {
vs: uriFromPath(path.join(__dirname, '../node_modules/monaco-editor/min/vs')),
},
});
关于这个主题,还有几个相关问题可能也会有所帮助 - 1 2 3 4
此外,有一篇博客文章介绍了如何在 Electron 的离线模式下使用 @monaco-editor/react。您可能会觉得它很有用。
如果您在 electron 中结合使用 monaco 和 react 时遇到了上述未描述的问题,请告诉我们,以便我们改进此部分内容,使其更具帮助性。
针对 Next.js 用户
与其他 React 组件一样,本组件在 Next.js 中也能顺利工作。源代码中需要预解析的部分已针对服务器端渲染进行了优化,因此在通常情况下都能正常运行。但如果您想访问例如monaco instance,则需要注意它会访问 document 对象,这需要浏览器环境。基本上,您只需避免在浏览器环境之外运行该部分代码即可,有多种方法可以实现这一点。其中一种方法在这里有描述。
如果您在 Next.js 中使用 monaco 时遇到了上述未描述的问题,请告诉我们,以便我们改进此部分内容,使其更具帮助性。
创建您自己的编辑器
此库在底层使用了@monaco-editor/loader,它提供了一个名为 loader 的工具。loader 工具是一组函数的集合,用于将 monaco 编辑器设置到您的浏览器中。loader.init() 负责处理整个初始化过程,并返回 monaco 的实例 - loader.init().then(monaco => console.log("here is the monaco instance:", monaco))。Editor 组件就是使用了这个工具,获取 monaco instance 并创建编辑器。这里是 Editor 组件的实现。您可以使用相同的技术来创建自己的 Editor。您只需导入 loader 工具,获取 monaco instance,然后使用您自己的自定义逻辑创建编辑器。实现此目的的最简方法如下:
import loader from '@monaco-editor/loader';
loader.init().then((monaco) => {
const wrapper = document.getElementById('root');
wrapper.style.height = '100vh';
const properties = {
value: 'function hello() {\n\talert("Hello world!");\n}',
language: 'javascript',
};
monaco.editor.create(wrapper, properties);
});
就是这些了。你可以将它包装成一个 React 组件、Vue 组件、Angular 组件,或者保留为原生 JavaScript 形式,随你喜欢;它是用纯 js 编写的。
开发环境 - 实验场
拥有一个可以探索库内部机制的地方总是很重要的。playground 是一个极简的 React 应用,它直接使用该库的源代码。因此,如果你打算提交 PR、检查某些内容,或者只是想尝试库的最新状态,你可以运行这个实验场并尽情使用。
- 克隆仓库
git clone https://github.com/suren-atoyan/monaco-react.git
- 进入库文件夹
cd monaco-react
- 安装库的依赖项
npm install # yarn
- 前往代码游乐场
cd playground
- 安装该演示环境的依赖项
npm install # yarn
- 并运行演示环境
npm run dev # yarn dev
monaco-react ├── playground │ ├── src/ # 示例应用源代码 ├── src/ # 库源代码 └── ...
如果您想修改库中的内容,请前往 monaco-react/src/...,库将自动重新构建,并且示例应用会使用最新的构建版本
属性
Editor
| 名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| defaultValue | string | 当前模型的默认值 | |
| defaultLanguage | string | 当前模型的默认语言 | |
| defaultPath | string | 当前模型的默认路径。将作为第三个参数传递给 .createModel 方法 - monaco.editor.createModel(..., ..., monaco.Uri.parse(defaultPath)) |
|
| value | string | 当前模型的值 | |
| language | enum: ... | 当前模型的语言(所有 支持的语言) | |
| path | string | 当前模型的路径。将作为第三个参数传递给 .createModel 方法 - monaco.editor.createModel(..., ..., monaco.Uri.parse(defaultPath)) |
|
| theme | enum: "light" | "vs-dark" | "light" | Monaco 的主题。可用选项为 "vs-dark" | "light"。通过 monaco.editor.defineTheme 定义新主题 |
| line | number | 要跳转到的行号 | |
| loading | React Node | "Loading..." | 编辑器挂载前的加载屏幕 |
| options | object | {} | IStandaloneEditorConstructionOptions |
| overrideServices | object | {} | IEditorOverrideServices |
| saveViewState | boolean | true | 指示在模型更改之间是否保存模型的视图状态 |
| keepCurrentModel | boolean | false | 指示编辑器卸载时是否销毁当前模型 |
| width | union: number | string | "100%" | 编辑器包装器的宽度 |
| height | union: number | string | "100%" | 编辑器包装器的高度 |
| className | string | 编辑器容器的类名 | |
| wrapperProps | object | {} | 应用于包装器元素的属性 |
| beforeMount | func | noop | 签名: function(monaco: Monaco) => void 编辑器挂载前触发的事件。第一个参数为 monaco 实例 |
| onMount | func | noop | 签名: function(editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) => void 编辑器挂载时触发的事件。第一个参数为 editor 实例,第二个参数为 monaco 实例 |
| onChange | func | 签名: function(value: string | undefined, ev: monaco.editor.IModelContentChangedEvent) => void 当前模型内容更改时触发的事件 |
|
| onValidate | func | noop | 签名: function(markers: monaco.editor.IMarker[]) => void 当前模型内容更改且当前模型标记就绪时触发的事件 |
DiffEditor
| 名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| original | string | 原始源(左侧)的值 | |
| modified | string | 修改源(右侧)的值 | |
| language | enum: ... | 两个模型(原始模型和修改模型)的语言(所有 支持的语言) | |
| originalLanguage | enum: ... | 此属性使您可以单独指定原始源的语言,否则,它将使用 language 属性的值 | |
| modifiedLanguage | enum: ... | 此属性使您可以单独指定修改源的语言,否则,它将使用 language 属性的值 | |
| originalModelPath | string | “原始”模型的路径。将作为第三个参数传递给 .createModel 方法 - monaco.editor.createModel(..., ..., monaco.Uri.parse(originalModelPath)) |
|
| modifiedModelPath | string | “修改”模型的路径。将作为第三个参数传递给 .createModel 方法 - monaco.editor.createModel(..., ..., monaco.Uri.parse(modifiedModelPath)) |
|
| keepCurrentOriginalModel | boolean | false | 指示 DiffEditor 卸载时是否销毁当前原始模型 |
| keepCurrentModifiedModel | boolean | false | 指示 DiffEditor 卸载时是否销毁当前修改模型 |
| theme | enum: "light" | "vs-dark" | "light" | Monaco 的主题。可用选项为 "vs-dark" | "light"。通过 monaco.editor.defineTheme 定义新主题 |
| line | number | 要跳转到的行号 | |
| loading | React Node | "Loading..." | 编辑器挂载前的加载屏幕 |
| options | object | {} | IDiffEditorConstructionOptions |
| width | union: number | string | "100%" | 编辑器包装器的宽度 |
| height | union: number | string | "100%" | 编辑器包装器的高度 |
| className | string | 编辑器容器的类名 | |
| wrapperProps | object | {} | 应用于包装器元素的属性 |
| beforeMount | func | noop | 签名: function(monaco: Monaco) => void 编辑器挂载前触发的事件。第一个参数为 monaco 实例 |
| onMount | func | noop | 签名: function(editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) => void 编辑器挂载时触发的事件。第一个参数为 editor 实例,第二个参数为 monaco 实例 |