一、业务需求
最近项目上有个需求是:开发一个简洁版的在线sql编辑器,其中一个要实现的功能是:当选中页面中一段sql语句,进行脚本转换,在弹窗里面也要显示选中的sql语句,并且可编辑,同时也要动态显示行号。
最终实现的效果如图所示:
二、思路及功能实现
本项目使用CodeMirror5x版本,因为用到编辑器的地方很多,所以我封装了一个CodeMirror编辑器组件,在需要引用的地方引入编辑器子组件就可以了。
要实现上述需求,我的思路是:在弹窗和页面中都引入了编辑器子组件。当选中页面中的编辑器子组件里的一段文本时、触发CodeMirror的cursorActivity事件,通过此事件把选中的代码、传入弹窗里的编辑器子组件v-model绑定的值,这样就会与之对应。编辑时如果想要获取实时代码、可通过CodeMirror的input事件给v-model绑定的值赋值。
页面中引入编辑器子组件如下所示:
<codemirror-editor
ref="myCm"
:mode="cmMode"
@input="onEditorInput"
@selectRange="handleSelectChange">
</codemirror-editor>
弹窗里引入编辑器子组件如下所示:
<FormDialog
title="脚本转换"
:dialogFlag.sync="showReplaceModal"
width="50%"
@cancel="replaceCancel">
<div class="formDialog" slot="content">
<el-form class="translateScriptForm">
<el-form-item label="选中的脚本:" class="selectedScriptItem" >
<codemirror-editor ref="replaceCodemirrorCm" v-model="selectedText" @input="onSelectedInput"></codemirror-editor>
</el-form-item>
<el-form-item class="translateBtnTextItem">
<el-button type="text" @click="handleTranslate">转换</el-button>
</el-form-item>
<el-form-item label="脚本转换结果:" class="translateResultItem">
<el-input type="textarea" v-model="replacedText" readonly class="replaceTextarea"></el-input>
</el-form-item>
</el-form>
</div>
<div slot="footer" class="dataDialog-footer">
<el-button class="confirmBtn " v-show="isFature" type="primary" @click="replaceConfirm">替换</el-button>
<el-button class="confirmBtn singleSelectBtn" @click="replaceCancel">关闭</el-button>
</div>
</FormDialog>
三、问题描述
需求是实现了,但是遇到一个问题:当我选中一段sql语句时,弹窗里初次显示的值是选中的,但是当我再次选中时,弹窗里的子组件值显示的还是上次选中的值。
四、原因分析
经过排查,发现弹窗里的子组件值改变了,视图没变化,所以需要刷新子组件视图,更新dom
五、解决方案
首先我想到了Vue.nextTick()回调函数
(1)监听编辑器子组件的值变化,使用Vue的nextTick异步更新dom
我以为子组件可以刷新视图了,结果还是没有
emma~~
突然想到选中的代码要在弹窗中相对应的显示,这是一个实时更新的过程,异步更新是不起作用的,soga
然后我就想到了第二种方案
(2)Vue有个特殊的attribute key,key的作用是:高效更新虚拟dom,可以强制替换元素/组件而不是重复的使用它。key值变化之后,会自动重新渲染组件。
于是我在弹窗编辑器子组件里加个key属性,key属性的值可以是字符串、时间戳、布尔值等,这里我设置的是时间戳timer。然后我在弹窗出现事件里给timer赋值,在弹窗关闭事件里给timer置空。这样问题就解决了,每次选中一段代码,弹窗中就实时显示选中的代码了。
<codemirror-editor ref="replaceCodemirrorCm" :key="timer" v-model="selectedText" @input="onSelectedInput">
</codemirror-editor>
// 脚本转换按钮事件
handleCodeTranslate(){
this.showReplaceModal = true;
//根据时间戳标识,刷新子组件
this.timer = new Date().getTime();
},
// 脚本转换弹窗取消事件
replaceCancel(){
// 清空脚本转换弹窗里的数据
this.selectedText = '';
this.replacedText = '';
this.timer = null;
// 去除页面上编辑器选中的标记
let editor = this.variableEditor;
// 设置光标位置,取消原来的选中状态
editor.setCursor({line:0,ch:0})
// 关闭弹窗
this.showReplaceModal = false;
},