关于@monaco-editor/react 自定义代码提示的一些踩坑日记
背景
大家好,我是刚毕业的一名页面仔,最近在日常工作中遇到了需要代码编辑器组件,这里我经过深思熟虑(其实也没有,就是小小的百度了一下),最终决定使用@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;
以上实现了代码编辑器的自定义提示功能,效果如下:
你以为这样就结束了,你们太小看产品经理了。
需求Plus
目前我们只是实现了自定义代码提示,没有任何交互,现在我们需要控制编辑器的显示和隐藏(这不就是加个变量控制一下吗?)ps:别着急,现在才刚刚开始~
代码
其实就是加一个开关控制编辑器的显示和隐藏,但是,当我们反复切换开关时bug就出现了...
提示重复了?? 因为我们使用开关控制编辑器的显示和隐藏会重复触发onMount方法,导致registerCompletionItemProvider实例会重复创建?
解决方案
我们可以再beforeMount里先去销毁registerCompletionItemProvider实例,这里还是要多读文档啊,这里附上文档链接
啊? 就这? 就这? 还不是自己开发的时候不仔细看文章,就这你出来发文章? 事情没有那么简单,有请我们可爱的产品经理!
需求Plus&&Pro
在同一个页面需要两个编辑器,给出两个不同
的提示,同时两个编辑器都需要控制他的显示和隐藏!!
(PS:有同学看到这里,笑了,这不就是copy一份吗,能有什么问题,最开始我心里和大家一样!拿出看家本领,CV大法!)
冲冲冲
这是我第一版代码,通过编辑器不同的language语言来实现代码,不同编辑器的不同代码提示,公用一个Mount方法,但是问题来了,这样写了之后,出现了下面的情况
这里通过language语言来实现两个编辑器不同的提示,来区分哪个编辑器使用哪个语言的模板,其实Mount返回的
moaco实例是全局共享的
(划重点!!!!),当前页面所有编辑器共享。
我只有第二个编辑器有提示,第一个没有???(还有个一个小细节,这里我把其中某个编辑器隐藏,再打开,就有提示了,但是另外一个就没有了)
排查问题
-
首先在初次渲染的时候,第二个编辑器的mount方法,最后执行,他们直接肯定是有个顺序的。(是我公用一个Mount方法的原因吗?覆盖掉了?)
-
切换其中一个编辑器,另一个编辑器提示就没了,这里越来越觉得是覆盖掉了
其实不是覆盖掉了,我最开始忽略了beforeMount(因为我们在这个周期里做了销毁registerCompletionItemProvider实例)这个周期,其实在第二个编辑器渲染的时候,首先会销毁registerCompletionItemProvider实例,再执行Mount方法注册。。(这里有同学就说了,那把两个Mount方法分开写,用不同
editorRef
去存储,不就解决问题了吗?最开始我也是这样想的!)
ps:当我把两个方法都隔离开,初次渲染时两个编辑器,问题依旧没有解决,感兴趣的同学可以去试试!!
总结:所以问题的根本原因是我在beforeMount
的时候我去销毁registerCompletionItemProvider
实例, 所以在第二个编辑器创建的时候,把第一个编辑器使用的language提示模板给销毁了,然后通过控制显示隐藏的时候,又执行了当前这个编辑器的beforeMount
和Mount
方法。
找到问题了。。怎么解决呢??
我的解决方案
首先在beforeMount
里销毁实例是肯定不行了(之前是为了解决提示有重复的bug加的,忘记的小伙伴往上翻!!)
这里我全局定义了一个空数组,在每次执行Mount的时候先检查数组里有没有当前的type
(ps:其实就是language,这里名字取得不是很好),没有就去创建实例,并且往这个数组里push相应的type,如果有就不再去创建了。
废话不多bb,直接上代码!
终于解决了,效果很完美!
总结
以上是我在使用@monaco-editor/react
插件时遇到了一些小坑,文章有点啰嗦,但都是我的心路历程,最后的解决方案有瑕疵,还不是很完美,假如自定义提示是后端返回的并且这个提示在页面上有联动,这个方案就有问题,可能就要在每次获取字段那里去清空数组editorArray
(或者其中的某一个语言),
其实万恶之源就在于,registerCompletionItemProvider
会重复创建,也就是我们解决的第一个问题,不知道这算不算官方的设计缺陷,各位大佬们如果有更好的解决方案,欢迎在评论区讨论分享。
最后求个三连,第一次发技术分享,求大佬们给点动力!!!
最后附上我的demo地址:gitee
谢谢大家,我是一名刚毕业的页面仔。
转载自:https://juejin.cn/post/7091880890174799903