react-monaco-editor编辑器安装使用&自定义提示


2021/02/28更
此次更新主要增加了给编辑器鼠标焦点处设置值方法

  /**
     * 给编辑器设置值
     * @param value 需要设置的值
     */
    const handleSetEditorVal = (value: string): void => {
      if (!value) return;
      // 为所选取的值赋值到编辑器中
      if (editorInstance.current && value) {
        const selection = editorInstance?.current?.getSelection?.();
        const range = new _monaco.Range(
          selection.startLineNumber,
          selection.startColumn,
          selection.endLineNumber,
          selection.endColumn
        );
        const id = { major: 1, minor: 1 };
        const op = { identifier: id, range, text: value, forceMoveMarkers: true };
        editorInstance.current.executeEdits('', [op]);
        editorInstance.current.focus();
      }
    };

开始

最近产品上用到了代码编辑器,因为技术栈使用的是react, 找了许久经过甄选最后决定使用monaco-editor的react封装版本:react-monaco-edotor,下面是他的简单介绍:


image.png

经过很多前辈大佬的努力,react-monaco-edotor横空出世,对于使用react技术栈的人是一个再好不过的好消息了,介绍完毕我们讲讲如何使用吧。

使用方法:

yarn add monaco-editor -D 
yarn add react-monaco-editor -D 
yarn add monaco-editor-webpack-plugin -D 

安装好依赖包再去webpack-config中配置相关依赖:

const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
...
 plugins: [
      new MonacoWebpackPlugin(['apex', 'azcli', 'bat', 'clojure', 'coffee', 'cpp', 'csharp', 'csp', 'css', 'dockerfile', 'fsharp', 'go', 'handlebars', 'html', 'ini', 'java', 'javascript', 'json', 'less', 'lua', 'markdown', 'msdax', 'mysql', 'objective', 'perl', 'pgsql', 'php', 'postiats', 'powerquery', 'powershell', 'pug', 'python', 'r', 'razor', 'redis', 'redshift', 'ruby', 'rust', 'sb', 'scheme', 'scss', 'shell', 
'solidity', 'sql', 'st', 'swift', 'typescript', 'vb', 'xml', 'yaml']),
...
]

注意:自定义提示时一定需设置 language: 'plaintext' 自定义文本, 并且在monaco.languages.registerCompletionItemProvider中使用深拷贝
经过以上步骤 编辑器就能正常应用到项目中去了,使用及自定义提示的使用方法:

import React, { useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Icon, Tooltip } from 'choerodon-ui/pro';
import MonacoEditor from 'react-monaco-editor';
import * as _monaco from 'monaco-editor';

import styles from './index.less';

let provider = {
  dispose: () => {},
};
interface IRightContent {
  currentRecord: any; // fixme
  handleCheck: () => Promise<boolean>;
  secondRightData: (model.dataSource.BaseDataSourceField | model.dataSource.BaseDataSourceHeader)[];
}
export default forwardRef(
  ({ currentRecord, handleCheck, secondRightData = [] }: IRightContent, ref) => {
    // 编辑器实例
    const editorInstance = useRef<any>();

    // 真实数据
    const code: any = useRef(currentRecord ? currentRecord.formulaContent : '');

    // 缓存数据
    const cache: any = useRef(code.current);

    // 输入引号触发页面刷新
    const [refresh, setRefresh] = React.useState<boolean>(false);

    const monacoInstance: any = useRef();
    const options = {
      selectOnLineNumbers: true,
      renderSideBySide: false,
    };

    useImperativeHandle(ref, () => ({
      handleSetEditorVal,
      getEditorData: () => cache.current,
    }));

    useEffect(
      () => () => {
        provider.dispose(); // 弹窗关闭后 销毁编辑器实例
      },
      []
    );

    /**
     * 给编辑器设置值
     * @param value 需要设置的值
     */
    const handleSetEditorVal = (value: string): void => {
      if (!value) return;
      // 为所选取的值赋值到编辑器中
      if (editorInstance.current && value) {
        const selection = editorInstance?.current?.getSelection?.();
        const range = new _monaco.Range(
          selection.startLineNumber,
          selection.startColumn,
          selection.endLineNumber,
          selection.endColumn
        );
        const id = { major: 1, minor: 1 };
        const op = { identifier: id, range, text: value, forceMoveMarkers: true };
        editorInstance.current.executeEdits('', [op]);
        editorInstance.current.focus();
      }
    };

    /**
     * 编辑器change回调
     * @param {String} val 当前编辑器的值
     */
    const onChangeHandle = (val: string, event: { changes: { text: any }[] }) => {
      const curWord = event.changes[0].text;
      if (curWord === '"') {
        cache.current = val + curWord;
        setRefresh(!refresh); // 刷新页面
        return;
      }
      cache.current = val;
    };

    /**
     * 初始化编辑器
     * @param {*} editor 编辑器实例
     * @param {*} monaco
     */
    interface ISuggestions {
      label: string;
      kind: string;
      insertText: string;
      detail?: string;
    }
    const editorDidMountHandle = (editor: any, monaco: any) => {
      monacoInstance.current = monaco;
      editorInstance.current = editor;
      const newSecondRightFields: model.dataSource.BaseDataSourceHeader[] = [];
      (secondRightData as model.dataSource.BaseDataSourceHeader[]).forEach((record) => {
        if (record.fields && Array.isArray(record.fields)) {
          record.fields.forEach((item: any) => {
            // fixme
            newSecondRightFields.push(item);
          });
        }
        code.current = newSecondRightFields; // 数组长度永远为1
      });
      // 提示项设值
      provider = monaco.languages.registerCompletionItemProvider('plaintext', {
        provideCompletionItems() {
          const suggestions: ISuggestions[] = [];
          if (code && code.current) {
            code.current.forEach((record) => {
              suggestions.push({
                // label未写错 中间加空格为了隐藏大写字段名称 大写字段名称用于规避自定义提示不匹配小写的bug
                label:
                  record.label ||
                  `${record.displayName} (${
                    record.aliasName
                  })                        ${''}(${record.aliasName.toUpperCase()})`, // 显示名称
                kind: record.kind || monaco.languages.CompletionItemKind.Field, // 这里Function也可以是别的值,主要用来显示不同的图标
                insertText: record.insertText || record.aliasName, // 实际粘贴上的值
                // detail: record.detail || `(property) ${record.aliasName}: String`,
              });
            });
          }
          [
            'CASEWHEN(expression1, value1, expression2, value2, ..., else_value)',
            'CONCAT(str1, str2, ...)',
            'ISNULL (expression, defaultValue)',
            'DATEDIFF_YEAR(startdate,enddate)',
            'DATEDIFF_MONTH(startdate,enddate)',
            'DATEDIFF_DAY(startdate,enddate)',
            'SUM(expression)',
            'AVG(expression)',
            'MAX(expression)',
            'MIN(expression)',
            'COUNT(expression)',
            'DISTINCTCOUNT(expression)',
            'DISTINCTAVG(expression)',
            'DISTINCTSUM(expression)',
            'NOW()',
          ].forEach((item) => {
            suggestions.push(
              // 添加contact()函数
              {
                label: item, // 显示名称
                kind: monaco.languages.CompletionItemKind.Function, // 这里Function也可以是别的值,主要用来显示不同的图标
                insertText: item, // 实际粘贴上的值
              }
            );
          });
          return {
            suggestions, // 必须使用深拷贝
          };
        },
        quickSuggestions: false, // 默认提示关闭
        // triggerCharacters: ['$', '.', '='], // 触发提示的字符,可以写多个
      });
      editor.focus();
    };

    return (
      <React.Fragment>
        <MonacoEditor
          width="100%"
          height="400px"
          language="plaintext"
          value={cache.current}
          options={options}
          onChange={onChangeHandle}
          editorDidMount={editorDidMountHandle}
        />
        <Tooltip title="校验" placement="top">
          <Icon type="" className={styles['data-check-icon']} onClick={handleCheck} />
        </Tooltip>
      </React.Fragment>
    );
  }
);

页面效果:


image.png

备注:
( 使用react-monaco-editor )还有很多坑,网上的介绍文档也不多,代码全靠摸索,这里我遇到的一个最大的坑就是自定义提示时,不能匹配小写字母,目前已规避这个问题,并且在gitHub上给作者提了issue,多发现问题并分享出来,程序世界会越来越美好。
react-monaco-editor作者地址:https://github.com/jaywcjlove/react-monacoeditor/issues/26

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350