前言⛹️♀️⛹️♀️
在上一篇文章中,我们成功的封装了tabs组件,这个组件我们实现了切换标题显示不同内容的功能,在一般状态正常使用下是没有什么问题,但是当我使用了echatrs的时候会出现问题
正常使用tabs情况下
可见这个是可以正常使用的,一般情况下没有问题,可以进行正常的渲染
代码片段:
引入echarts使用出错🤔🤔
问题一:未设置高度🥉🥉
但是当我引入echarts时,就出现了一些错误:这里只展示setup部分的代码
setup() { // 这里需要获取tab-panel的DOM元素 const ecart1 = ref<Ref>() const ecart2 = ref<Ref>() const ecart3 = ref<Ref>() onMounted(() => { const echart1 = echarts.init(ecart1.value as any) echart1.setOption({ //...一些echarts的配置 }) }) return { ecart1, ecart2, ecart3, }
此时在控制台报了这样的错误:
这个错误表明我们没有给盒子一个宽高导致的,那么我们可以给盒子一个宽高
问题二:未正确获取DOM🎃🎃
使用v-if遇到的问题
当时使用echarts的时候,我认为直接在onMounted中将echarts挂载到DOM元素中,这样应该就可以实现切换渲染图片,但是却会报错误:
这里面报错误是因为底层使用的是v-if来判断是否选中当前元素。而在初始化的时候,由于我们使用echarts注入了两个DOM结构,我们知道,v-if与v-show是不同的v-if是是否渲染的问题,而v-show是是否显示的问题,底层是修改display:none属性来实现的。
报出这样的错误其实可能是有一下几个原因:
- 可能是将初始化放在vue的created阶段,因为这个时候dom还没挂载,所以获取不到,解决方法是把初始化放在mounted阶段来执行。
- 还可能是 插入的元素的父元素或者祖先元素中有v-if(有条件渲染),而v-if绑定的条件绑定的变量是和后端返回的数据有关的,这样可能会出现dom未更新时图表就开始初始化,也会获取不到DOM。
- Vue的DOM是异步更新的,只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。
而我们在首次渲染的时候,只渲染了默认选中的那个页面,其他页面还没有渲染,因此另一个DOM还没渲染完毕,就会报出一个这样的错误 ,显然犯了上述的第二个原因。
如果我们改成v-show呢? 🛰🛰
会报出这个错误:
出现了下面的这个问题:
这个问题是为什么呢?
由于你使用了v-show,那么这个时候使用的是通过给每个元素修改display:none属性来控制显示和隐藏,那么当某个元素显示其他元素隐藏时,这个时候无法获取高度,因此会报出这个错误。因此这个虽然渲染出来了元素,但是依然无法实现相应的功能。
如何解决? 🚴♀🚴♀
因此现在我萌生了一个想法,就是能不能定义一个点击事件,在刚开始渲染的时候调用一次点击事件,然后在每次切换的时候调用组件渲染相应的echarts模块,这样,我们不就能解决问题了吗
// 定义点击事件,接收子组件的数据 const handelTabs = (data: any) => { init(data) } // 定义一个init函数,用于判断当前点击的标题元素 const init = (value: string) => { if (value === 'first') { const echart1 = echarts.init(ecart1.value as any) echart1.setOption({ //...配置echarts }) } else if (value === 'second') { const echart2 = echarts.init(ecart2.value as any) echart2.setOption({ //...配置echarts }) } } onMounted(() => { // 默认第一次挂载的时候,调用函数 handelTabs((tabs.value as any).default) })
这样就完成了我们上面所说的思想,但是这样还是会遇到问题,这是为什么呢?
首先我们来看这个handelTabs函数,这个函数被调用后会执行init函数,但是当切换的时候,因为我们使用的是v-if来判断,此时还没有渲染出来DOM元素,此时依然会报Uncaught Error: Initialize failed: invalid dom.这个错误,那么这又该如何解决呢?
nextTick的使用⚡️⚡️
首先解释一下为什么会出现这个情况:这里面就要设计到Vue数据更新的原理了(异步更新队列)
Vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。然后再下一个事件循环tick中,Vue刷新队列并执行实际(已去重的)工作。所以如果用一个for循环来动态改变数据100次,其实它只会应用最后一次改变,如果没有这种机制,DOM就要重绘100次,过于耗费资源。
Vue会根据当前浏览器环境优先使用原生的Promise.then和MutationObserver,如果都不支持,就会采用setTimeout代替。
因此当你设置 改变了一个新数据data,DOM 并不会马上更新,而是在异步队列中,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。
简单介绍一下nextTick:
定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
理解:nextTick(),是将回调函数延迟在下一次DOM更新数据后调用,简单的理解是:当数据更新了,在DOM中渲染后,自动执行该函数,
人话: 也就是说这样就可以在DOM更新完成之后就是执行回调,这样就可以更新完视图了
因此handelTabs函数要这样修改:
// 定义点击事件,接收子组件的数据 const handelTabs = (data: any) => { nextTick(() => { init(data) }) }
这样就可以完成视图的更新,成功的渲染出来echarts了
总结 🥈🥈
解决了在使用tabs组件和echarts结合使用的时候会遇到的问题,并且还复习了nextTick的使用,我认为学习过程中可以及时的总结,找到问题的原因所在,然后解决它,这样是效率最高的学习方式