react-codemirror 入门使用

10,542 阅读3分钟

react-codemirror文档:github.com/uiwjs/react…

本文档主要记录自己 codeMirror 的入门过程,内容分为以下四部分:

  1. 在 react 中引入 codeMirror;
  2. 加载现有组件代码到 codeMirror中;
  3. 在线运行codeMirror中的代码,并渲染到页面中;
  4. 在线编辑codeMirror中代码,并将代码运行效果渲染到页面右侧区域;
  5. 给编辑器添加代码提示;

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 中。步骤如下:

  1. 先创建一个简单的 react 组件,File.jsx;
  2. 然后通过require(`!raw-loader!./File`).default 加载 File.jsx 组件的代码,此处获取代码的字符串,并将其赋值给 state.code;
  3. 将 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 组件。步骤如下:

  1. 使用 babel 的 transform 将 ES6 代码和 react 转化为浏览器可运行的代码;
  2. 使用 Funtion 执行转化后的代码,生成组件;
  3. 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 中的代码。步骤如下:

  1. 给 codeMirror 组件添加上 onChange 监听事件,当编辑代码代码时,将代码字符串更新到 state.code 中
  2. 在页面添加一个“运行”按钮,给代码添加 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>
    );
  }
}
效果图

五、给编辑器添加代码提示

  1. 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'] });
}}
  1. 打开 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;
}
效果图