likes
comments
collection
share

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

作者站长头像
站长
· 阅读数 143

背景

大家好,我是刚毕业的一名页面仔,最近在日常工作中遇到了需要代码编辑器组件,这里我经过深思熟虑(其实也没有,就是小小的百度了一下),最终决定使用@monaco-editor/react来实现编辑器功能。

需求分析

现在需要做一个代码编辑器,并给出良好的自定义代码提示(实际业务中,这个提示是根据后端返回的),话不多说,上菜!

基础使用

import React, {  useRef } from 'react';
import './index.less';
import MonacoEditor from '@monaco-editor/react';
const Index = () => {
  const editorRef = useRef();
  const transSug = (items) => {
    const newSug = [...items, 'and', 'or', '(', ')'].map((item) => {
      return {
        label: item, // 显示的label
        detail: !items.includes(item) ? '符号' : '字段', // 描述
        insertText: item, // 选择后插入的value
        icon: items.includes(item),
      };
    });
    return newSug;
  };
  const editorDidMount = (editor, monaco) => {
    const suggestions = transSug(['代码提示']);
    if(suggestions.length){
    editorRef.current = monaco.languages.registerCompletionItemProvider(
      'plaintext',
      {
        provideCompletionItems() {
          return {
            suggestions: suggestions.map((item) => ({
              ...item,
              kind: item.icon
                ? monaco.languages.CompletionItemKind.Variable // 图标
                : null,
            })),
          };
        },
        triggerCharacters: [' '], // 触发代码提示的关键字,ps:可以有多个
      },
    );
   }
  };
  return (
    <div>
      <MonacoEditor
        width="500px"
        theme="vs-dark"
        height="300px"
        language="plaintext" //注意此处language必须与 monaco 注册的代码提示里的保持一致
        onMount={editorDidMount}
      />
    </div>
  );
};

export default Index;

以上实现了代码编辑器的自定义提示功能,效果如下: 关于@monaco-editor/react 自定义代码提示的一些踩坑日记

你以为这样就结束了,你们太小看产品经理了。

需求Plus

目前我们只是实现了自定义代码提示,没有任何交互,现在我们需要控制编辑器的显示和隐藏(这不就是加个变量控制一下吗?)ps:别着急,现在才刚刚开始~

代码

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

其实就是加一个开关控制编辑器的显示和隐藏,但是,当我们反复切换开关时bug就出现了...

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

提示重复了?? 因为我们使用开关控制编辑器的显示和隐藏会重复触发onMount方法,导致registerCompletionItemProvider实例会重复创建?

解决方案

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

我们可以再beforeMount里先去销毁registerCompletionItemProvider实例,这里还是要多读文档啊,这里附上文档链接

啊? 就这? 就这? 还不是自己开发的时候不仔细看文章,就这你出来发文章? 事情没有那么简单,有请我们可爱的产品经理!

需求Plus&&Pro

在同一个页面需要两个编辑器,给出两个不同的提示,同时两个编辑器都需要控制他的显示和隐藏!! (PS:有同学看到这里,笑了,这不就是copy一份吗,能有什么问题,最开始我心里和大家一样!拿出看家本领,CV大法!)

冲冲冲

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

这是我第一版代码,通过编辑器不同的language语言来实现代码,不同编辑器的不同代码提示,公用一个Mount方法,但是问题来了,这样写了之后,出现了下面的情况

这里通过language语言来实现两个编辑器不同的提示,来区分哪个编辑器使用哪个语言的模板,其实Mount返回的 moaco实例是全局共享的(划重点!!!!),当前页面所有编辑器共享。

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

我只有第二个编辑器有提示,第一个没有???(还有个一个小细节,这里我把其中某个编辑器隐藏,再打开,就有提示了,但是另外一个就没有了)

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

排查问题

  1. 首先在初次渲染的时候,第二个编辑器的mount方法,最后执行,他们直接肯定是有个顺序的。(是我公用一个Mount方法的原因吗?覆盖掉了?)

  2. 切换其中一个编辑器,另一个编辑器提示就没了,这里越来越觉得是覆盖掉了

其实不是覆盖掉了,我最开始忽略了beforeMount(因为我们在这个周期里做了销毁registerCompletionItemProvider实例)这个周期,其实在第二个编辑器渲染的时候,首先会销毁registerCompletionItemProvider实例,再执行Mount方法注册。。(这里有同学就说了,那把两个Mount方法分开写,用不同editorRef去存储,不就解决问题了吗?最开始我也是这样想的!)

ps:当我把两个方法都隔离开,初次渲染时两个编辑器,问题依旧没有解决,感兴趣的同学可以去试试!!

总结:所以问题的根本原因是我在beforeMount的时候我去销毁registerCompletionItemProvider实例, 所以在第二个编辑器创建的时候,把第一个编辑器使用的language提示模板给销毁了,然后通过控制显示隐藏的时候,又执行了当前这个编辑器的beforeMountMount方法。

找到问题了。。怎么解决呢??

我的解决方案

首先在beforeMount里销毁实例是肯定不行了(之前是为了解决提示有重复的bug加的,忘记的小伙伴往上翻!!)

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

这里我全局定义了一个空数组,在每次执行Mount的时候先检查数组里有没有当前的type(ps:其实就是language,这里名字取得不是很好),没有就去创建实例,并且往这个数组里push相应的type,如果有就不再去创建了。

废话不多bb,直接上代码!

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

终于解决了,效果很完美!

关于@monaco-editor/react 自定义代码提示的一些踩坑日记

总结

以上是我在使用@monaco-editor/react 插件时遇到了一些小坑,文章有点啰嗦,但都是我的心路历程,最后的解决方案有瑕疵,还不是很完美,假如自定义提示是后端返回的并且这个提示在页面上有联动,这个方案就有问题,可能就要在每次获取字段那里去清空数组editorArray(或者其中的某一个语言), 其实万恶之源就在于,registerCompletionItemProvider会重复创建,也就是我们解决的第一个问题,不知道这算不算官方的设计缺陷,各位大佬们如果有更好的解决方案,欢迎在评论区讨论分享。

最后求个三连,第一次发技术分享,求大佬们给点动力!!!

最后附上我的demo地址:gitee

谢谢大家,我是一名刚毕业的页面仔。