react-codemirror文档:github.com/uiwjs/react…
本文档主要记录自己 codeMirror 的入门过程,内容分为以下四部分:
- 在 react 中引入 codeMirror;
- 加载现有组件代码到 codeMirror中;
- 在线运行codeMirror中的代码,并渲染到页面中;
- 在线编辑codeMirror中代码,并将代码运行效果渲染到页面右侧区域;
- 给编辑器添加代码提示;
install
安装代码编辑器组件
npm install --save @uiw/react-codemirror
raw-loader 在此用于加载现有组件的代码文件的字符串,更多 raw-loader 说明,请参考:cloud.tencent.com/developer/s…
npm install --save-dev raw-loader
@babel/standalone 在此主要用于转化 ES6 和 react 代码,更多关于@babel/standalone 说明,请参考:babeljs.io/docs/en/bab…
npm install --save-dev @babel/standalone
Usage
一、组件引入
此小节主要是介绍在我们 demo 中如何引入 codeMirror,及简单进行主题配置和代码格式配置,其他配置请参考 codeMirror 组件的 API 文档
完整代码
src/components/CodeMirrorDemo.jsx
import React, { Component } from "react";
import CodeMirror from "@uiw/react-codemirror";
import "codemirror/keymap/sublime";
import "codemirror/theme/monokai.css";
const code = "const a = 0;";
export default class CodeMirrorDemo extends Component {
render() {
return (
<CodeMirror
value={code}
options={{
theme: "monokai",
keyMap: "sublime",
mode: "jsx",
// 括号匹配
matchBrackets: true,
// tab缩进
tabSize: 2,
}}
/>
);
}
}
效果图
二、加载文件到 codeMirror
此小节主要是介绍如何将一个现有的,没有其他第三方依赖的 react 组件加载到 codeMirror 中。步骤如下:
- 先创建一个简单的 react 组件,File.jsx;
- 然后通过
require(`!raw-loader!./File`).default
加载 File.jsx 组件的代码,此处获取代码的字符串,并将其赋值给 state.code; - 将 state.code 赋给 codeMirror 组件的 value 属性。
完整代码如下:
创建要加载的文件
src/components/File.jsx
import React, { Component } from "react";
export default class FileTest extends Component {
render() {
return <h1 style={{ background: "red", color: "#fff" }}>This is a test</h1>;
}
}
引入要加载的文件
src/components/CodeMirrorDemo.jsx
import React, { Component } from "react";
import CodeMirror from "@uiw/react-codemirror";
import "codemirror/keymap/sublime";
import "codemirror/theme/monokai.css";
export default class CodeMirrorDemo extends Component {
state = {
code: "",
};
componentDidMount() {
this.setState({ code: require(`!raw-loader!./File`).default });
}
render() {
const { code } = this.state;
return (
<CodeMirror
value={code}
options={{
theme: "monokai",
keyMap: "sublime",
mode: "jsx",
// 括号匹配
matchBrackets: true,
// tab缩进
tabSize: 2,
}}
/>
);
}
}
效果图
三、在线运行加载的文件
此小节主要是介绍如何在线运行第二小节中载入的 react 组件。步骤如下:
- 使用 babel 的 transform 将 ES6 代码和 react 转化为浏览器可运行的代码;
- 使用 Funtion 执行转化后的代码,生成组件;
- React.createElement() 方法创建 React DOM,并将组件渲染到页面中。
需注意,步骤 2 中,转化后的代码中含有 exports,和 require('XX')方法,直接运行转化的代码会报找不到 exports 对象和 require 方法,所以我们在 Function 的方法体中,要先定义好 export 对象和 require 方法,此处直接通过参数传入到 Function 中,然后在方法体中定义好接收参数的变量,变量名为 exports 和 require。当前 demo 中,require 只定义了 react,react-dom,若是 codeMirror 编辑器中的代码有其他三方依赖,需要加入到这里,否则会报找不到该模块。
完整代码如下:
将 ES6 代码字符串转化浏览器可执行的代码,并生成组件
src/components/tool.js
import { transform } from "@babel/standalone";
const _require = moduleName => {
const modeules = {
react: require("react"),
"react-dom": require("react-dom"),
};
if (modeules[moduleName]) {
return modeules[moduleName];
}
throw new Error(
`找不到'${moduleName}模块',可选模块有:${Object.keys(modeules).join(", ")}`
);
};
export const evalCode = code => {
const output = transform(code, { presets: ["es2015", "react"] }).code;
const fn = new Function(
`var require = arguments[0], exports = arguments[1];\n ${output}`
);
const exports = {};
fn.call(null, _require, exports);
return exports.default;
};
执行加载的组件,并将组件渲染到页面
src/components/CodeMirrorDemo.jsx
import React, { Component } from "react";
import { evalCode } from "./tool";
import CodeMirror from "@uiw/react-codemirror";
import "codemirror/keymap/sublime";
import "codemirror/theme/monokai.css";
export default class CodeMirrorDemo extends Component {
state = {
code: "",
codeComponent: null,
};
componentDidMount() {
this.setState({ code: require(`!raw-loader!./File`).default }, () => {
this.evalCode();
});
}
evalCode = () => {
const { code } = this.state;
this.setState({ codeComponent: evalCode(code) });
};
render() {
const { code, codeComponent } = this.state;
return (
<div
style={{
display: "flex",
height: "100%",
maxHeight: "80vh",
position: "relative",
}}
>
<CodeMirror
value={code}
options={{
theme: "monokai",
keyMap: "sublime",
mode: "jsx",
// 括号匹配
matchBrackets: true,
// tab缩进
tabSize: 2,
}}
/>
<div style={{ width: "50%", background: "#fff" }}>
{codeComponent ? React.createElement(codeComponent) : null}
</div>
</div>
);
}
}
效果图
四、在线修改代码并运行
此小节主要介绍如何在线编辑并运行 codeMorror 中的代码。步骤如下:
- 给 codeMirror 组件添加上 onChange 监听事件,当编辑代码代码时,将代码字符串更新到 state.code 中
- 在页面添加一个“运行”按钮,给代码添加 onClick 事件,获取当前的代码字符串,按照第三小节步骤,将代码字符串进行转换执行,生成组件渲染到页面。
完整代码如下:
代码
src/components/CodeMirrorDemo.jsx
import React, { Component } from "react";
import { evalCode } from "./tool";
import CodeMirror from "@uiw/react-codemirror";
import "codemirror/keymap/sublime";
import "codemirror/theme/monokai.css";
export default class CodeMirrorDemo extends Component {
state = {
code: "",
codeComponent: null,
};
componentDidMount() {
this.setState({ code: require(`!raw-loader!./File`).default }, () => {
this.evalCode();
});
}
evalCode = () => {
const { code } = this.state;
this.setState({ codeComponent: evalCode(code) });
};
render() {
const { code, codeComponent } = this.state;
return (
<div
style={{
display: "flex",
height: "100%",
maxHeight: "80vh",
position: "relative",
}}
>
<a
onClick={this.evalCode}
style={{ position: "absolute", right: 10, top: 0, zIndex: 999 }}
>
运行
</a>
<CodeMirror
value={code}
options={{
theme: "monokai",
keyMap: "sublime",
mode: "jsx",
// 括号匹配
matchBrackets: true,
// tab缩进
tabSize: 2,
}}
onChanges={editor => {
this.setState({ code: editor.getValue() });
}}
/>
<div style={{ width: "50%", background: "#fff" }}>
{codeComponent ? React.createElement(codeComponent) : null}
</div>
</div>
);
}
}
效果图
五、给编辑器添加代码提示
- 给
CodeMirror
组件中,添加onCursorActivity
事件监听.
// 引入提示代码相关脚本
import "codemirror/addon/hint/show-hint";
import "codemirror/addon/hint/javascript-hint";
import "codemirror/addon/hint/show-hint.css";
// 添加事件监听
onCursorActivity={(editor) => {
//调用显示提示
editor.showHint({ customKeywords: ["componentDidMount", "componentWillUnmount", 'setState'] });
}}
- 打开 javascript-hint.js 文件,找到
getCompletions
方法,修改该方法,使编辑器在没有任何字母输入时也会有代码提示,并自定义一些关键字提示。
// 当没有输入时,不返回提示内容
if (token.string == "") {
return { list: {} };
}
// 添加自定义的关键字
var customKeywords = options.customKeywords || [];
forEach(customKeywords, maybeAdd);
完整的方法如下:
function getCompletions(token, context, keywords, options) {
//当没有输入时,不返回提示内容
if (token.string == "") {
return { list: {} };
}
var found = [],
start = token.string,
global = (options && options.globalScope) || window;
function maybeAdd(str) {
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str))
found.push(str);
}
function gatherCompletions(obj) {
if (typeof obj == "string") forEach(stringProps, maybeAdd);
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
// 添加自定义的关键字
var customKeywords = options.customKeywords || [];
forEach(customKeywords, maybeAdd);
forAllProps(obj, maybeAdd);
}
if (context && context.length) {
// If this is a property, see if it belongs to some object we can
// find in the current environment.
var obj = context.pop(),
base;
if (obj.type && obj.type.indexOf("variable") === 0) {
if (options && options.additionalContext)
base = options.additionalContext[obj.string];
if (!options || options.useGlobalScope !== false)
base = base || global[obj.string];
} else if (obj.type == "string") {
base = "";
} else if (obj.type == "atom") {
base = 1;
} else if (obj.type == "function") {
if (
global.jQuery != null &&
(obj.string == "$" || obj.string == "jQuery") &&
typeof global.jQuery == "function"
)
base = global.jQuery();
else if (
global._ != null &&
obj.string == "_" &&
typeof global._ == "function"
)
base = global._();
}
while (base != null && context.length) base = base[context.pop().string];
if (base != null) gatherCompletions(base);
} else {
// If not, just look in the global object and any local scope
// (reading into JS mode internals to get at the local and global variables)
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
for (var c = token.state.context; c; c = c.prev)
for (var v = c.vars; v; v = v.next) maybeAdd(v.name);
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
if (!options || options.useGlobalScope !== false) gatherCompletions(global);
forEach(keywords, maybeAdd);
}
return found;
}