v-if真的不能做切换么?

758 阅读4分钟

vue.webp

面试官:说下v-ifv-show的区别?

我:v-if是创建和销毁元素,v-show是用css的display属性控制元素的显隐

我想大多数人都是这么回答的,但是我总感觉少了点什么?

.........

v-if(以下简称if)在使用的时候要重新创建Dom,我们频繁的去显示和隐藏元素,这样导致性能消耗过大,所以这个时候要用v-show(以下简称show)。

感觉,频繁的创建一个元素,或者创建好几个元素确实让浏览器做了很多事情,但是对于强大的Chrome来说这好像不算什么(...此处禁止抬杠,什么要切换的内容很多很大怎么,怎么样,就要用show 不能用if)。

有一个开发场景,相信很多小伙伴都做过,就是用两个按钮去控制两个echarts图表的显示

01.png

我们分别用 if 和 show 来完成 看看if到底差哪了。

我们用获取dom来模拟echarts图的展示

写过echarts的小伙伴们都知道 我们借用echarts组件只需要获取一个dom容器传入相应配置echarts会帮我们完成一切

下面是主要代码:

      ....
    <el-button :type="!show && 'danger'" @click="handlebread">展示饼图 </el-button>
    <el-button :type="show && 'danger'" @click="handlepillars">展示柱状图</el-button>
      
    <div ref="breadChart" v-show="!show">饼图</div>
    <div ref="pillarsChart" v-show="show">柱状图</div>

    handlebread() {
      this.show = false
      this.$refs.breadChart.innerHTML = '饼图获取dom成功'
    },
    handlepillars() {
      this.show = true
      this.$refs.pillarsChart.innerHTML = '柱状图获取dom成功'
    },

用show来控制切换非常的丝滑,没有任何的问题,接下来把show换成if再试一下结果

代码只需稍作改动:

      ...
    <div ref="breadChart" v-if="!show">饼图</div>
    <div ref="pillarsChart" v-if="show">柱状图</div>

这个时候 再去切换的时候就出现了问题,发现每次切换的时候都必须要点击两下页面才会显示"xxDom获取成功", 问题出在哪里呢?

我们来分析一下这段代码:

<template>
  ...
 <div ref="breadChart" v-if="!show">饼图</div>
 ...
</template>

  handlebread() {
      this.show = false
      this.$refs.breadChart.innerHTML = '饼图获取dom成功'
    },
 

当变量show变成false的时候模板就会准备更新(创建饼图的div容器),接着往下运行去获取元素实例,这个时候this.$refs.breadChart的值是undefined,因为当前模板上面没有这个实例。

因为在一个函数中改变响应式的数据,它不会立马更新模板,而是在需要模板更新的地方做一个标记,等到这个函数的执行上下文全部执行完成以后再统一更新模板

vue这么做是很有道理的,假设在函数中有三处改变了响应式数据,难道解析就要停下来三次 更新三次模板,这样用户的体验肯定是极差的。

解决问题的方式很简单

handlebread() {
      this.show = false
      this.$nextTick(() => {
        this.$refs.breadChart.innerHTML = '饼图获取dom成功'
      })
    },

改动以后用if切换也会变得非常的丝滑。

this.$nextTick()就不过多赘述了,简单点说就是他会保证我们获取到最新的dom实例(这里是不是 再被问到$nextTick的应用场景可以举出上面的小例子去说明)

好不容易,让用if切换的时候也变得如此丝滑,这个时候产品走过来说,需求变了,原来的饼图不要了,我们让ui小姐姐做一些好看的图片当背景来展示数据

我微笑着说:好的(内心:'你****')

大概变成了这个样子(我用自己的例子还原一下现场)

1.png 也就是说一个tab不需要操作dom另一个需要操作dom

我们再用if 和show来写一下这个切换,我们只需要把handlebread里的内容修改一下不让他获取dom就可以了

handlebread() {
      this.show = false
     //把下面代码注掉即可
     // this.$nextTick(() => {
     // this.$refs.breadChart.innerHTML = '饼图获取dom成功'
     // })
    },

运行的结果是show一如既往的丝滑。

用if去切换的时候 当点击"展示柱状图"按钮,页面显示'柱状图获取dom成功'没有任何问题,再切回来的时候,...就切不回来了 页面一直显示的是'柱状图获取dom成功',我点击多少次都没有用,当时我当然用show把项目先写完了,但是为什么?却不知道,于是有了上面的例子 有了这篇文章.

(这个时候小伙伴们可以自己写一个Demo去感受一下,找找问题的原因,和解决的方法,下面开始分析解决)

<div ref="breadChart" v-if="!show">饼图</div>
<div ref="pillarsChart" v-else">柱状图</div>

这是显示两个tab的标签,理想状态是当点击"展示一段文字"按钮,销毁"柱状图"这个标签,创建"饼图"这个标签。vue在进行视图更新的时候采用diff算法去比较修改前后的差异采用"复用和移动的原则",并不是真的创建和销毁,但是复用和移动的前提是,在虚拟dom对象中准确的找到对应的节点才能比较,说白了他们两个让diff算法分不清谁是谁了,所以不能正确的创建和销毁.解决问题的方法很简单,难的往往是找到问题的原因.

//用不同的标签做tab
 <p ref="breadChart" v-if="!show">饼图</p>
<div ref="pillarsChart" v-else">柱状图</div>

//添加唯一标识key
<div key"a" ref="breadChart" v-if="!show">饼图</div>
<div key="b" ref="pillarsChart" v-else">柱状图</div>

以上两种方法都可让if变得非常丝滑.