带你了解vue源码与实用技巧(二)—— v-if vs v-show

开发过程中经常会遇到控制元素显示与隐藏,大家也都知道可以使用 v-if 或者 v-show 来达到显示与隐藏的效果,当然大家也都悉知,如果频繁需要显示与隐藏最好使用v-show,如果切换次数比较少,可以使用 v-if,那么具体是因为什么呐?两者之间的实现原理又有什么差别呐?

下面,我们就展开来说说吧,我们先来说他们之间的区别与使用场景,再来分析原理,最后说一下实用技巧。

v-show vs v-if 区别与使用场景

v-if 和 v-show 都是条件渲染

例1:

xml 复制代码
<template>
    <div>
        <div v-if="n">1</div>
        <div v-else>2</div>
    </div>
</template>

v-if 可以结合 v-else ,写出类似于 js 的渲染逻辑,比如:n = true,渲染1,n = false,渲染2

例2:

xml 复制代码
<template>
  <div>
    <p v-show="isShow">这是一个 v-show </p>
    <button @click="btn2">点击控制v-show</button>
  </div>
</template>

isShow = true,显示,为false不显示

区别:

  • 手段:
    • v-if 是动态的向 DOM 树内添加或者删除 DOM 元素;
    • v-show 是通过设置 DOM 元素的 display 样式属性控制显隐;
  • 编译过程:
    • v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;
    • v-show 只是简单的 css 切换;
  • 编译条件:
    • v-if 是惰性的,如果初始条件为假,则什么也不做,只有在条件为真时才开始局部编译;
    • v-show 是在任何条件下(不管首次条件是否为真)都会被编译,然后缓存,而且 DOM 元素被保留;
  • 性能消耗:
    • v-if 有更高的切换消耗;
    • v-show 有更高的初始渲染消耗;

使用场景:

一般来说 v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销,因此:

  • 如果是需要非常频繁的切换,则使用 v-show 比较好;
  • 如果在与运行时,条件很少改变,则使用 v-if 比较好;在需要重新生成新标签的时候使用 v-if

v-if vs v-show 实现原理

知道了他们之间的区别,就很好奇vue源码中具体是如何实现的呐,首先来看比较简单的 v-show 是如何实现的。

v-show

v-show 是运行时添加到 vue中的指令,截取show.js中部分代码如下:

这段代码,还是很好理解的,v-show 指令会处理两个逻辑,普通 v-show 和 transition 时的v-show 情况,我们只关注于普通 v-show 的情况即可,即会命中红色框中代码,value = false时, display = none,元素隐藏,否色不设置,元素显示。

如上例2,浏览器中查看渲染到页面中的HTML结果:

图1 isShow = false

图2 isShow = true

了解了v-show的实现原理,接下来看一下v-if的原理。

v-if 原理

v-if在实现上比v-show要复杂的多,因为还有else else-if 等条件需要处理,这里我们也只摘抄源码中处理 v-if 的一小部分。

我们知道,vue的编译挂载过程大致分为四个部分:

1、调用init,初始化参数;

2、检测到有el属性,则调用vm.$monut 方法挂在vm,最核心的 2 个方法:vm._render 和 vm._update

3、在mounted实现中,会将template 编译成render方法,执行 createElement 方法并返回的是 vnode,生成vnode

4、vm._update 中,会通过 vm.patch 函数将vnode渲染成真实DOM节点,并渲染出来

图3 初始化 Vue 到最终渲染的整个过程

本文将按这个流程分析v-if的处理过程,此时

xml 复制代码
<template>
  <div>
    <p v-if="isIf">这是一个 v-if </p>  
    <button @click="btn1">点击控制v-if</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isIf: false,
    };
  },
};
</script>

1、在 $mount 函数中将模板字符串编译为渲染函数,模板编译阶段,parseHTML 通过processIf 函数解析v-if指令,为元素添加if属性,其值为v-if对应的表达式

2、编译成render函数,生成vnode,通过patch生成真真实dom并渲染,

图4 template字符串编译生成的render函数

从 render 函数通过表达式可以看到,根据 isIf 的值来决定是否生成DOM,并且当值为false时,会创建一个注释节点用来占位,其目的是在不展示该元素的时候,标识其在页面中的位置,以便在 patch 的时候将该元素放回该位置。

3、isIf是响应式数据,当 isIf 动态切换true或者false的过程,会派发更新,更新节点,具体更新过程在之前的响应式与非相应是中有讲解,这里不在阐述。

浏览器中查看渲染到页面中的HTML结果:

图5 isIf = false

图6 isIf = true

总体来看,v-if 指令的主要是基于数据驱动的理念,当 v-if 指令对应的 value 为 false 的时候会预先创建一个注释节点在该位置,然后在 value 发生变化时,命中派发更新的逻辑,对新旧组件树进行 patch,从而完成使用 v-if 指令元素的动态显示隐藏。

图7 v-if 更新流程图

以上,我们将v-if 和 v-show 的原理简单讲解了一下,相信大家也更加明白第一部分中他们之间的区别于实用场景了。

实用技巧

只能使用 v-if

  1. 场景:在实现递归组件时,必须使用 v-if,如果使用 v-show 会造成堆栈溢出:
xml 复制代码
/* 递归组件 */
<template>
  <div :class="[!notFirst && 'tree']">
    <Tree v-if="length > 0" :length="length-1" :notFirst="true"/>
    <Tree v-if="length > 0" :length="length-1" :notFirst="true"/>
  </div>
</template>
<script>
export default {
  name: "Tree",
  props: ["length", "notFirst"]
};
</script>
<style scoped>
.tree {
  position: relative;
  margin: 400px auto;
  width: 100px;
  height: 100px;
  background: inherit;
}
div {
  background: #000;
  width: 1px;
  height: 70%;
  position: absolute;
  bottom: 100%;
  transform-origin: 50% 100%;
}
div > div:first-child {
  transform: rotate(-23deg);
}
div > div:last-child {
  transform: rotate(15deg);
}
</style>
xml 复制代码
<template>
    <Tree :length="length" :notFirst="notFirst"></Tree>
</template>
<script>
    import Tree from "./tree.vue"
    export default {
        components: {Tree},
        data() {
            return {
                notFirst: false,
                length: 7,
            }  
        }
    }
</script>

图8 二叉树

如果使用 v-show,会造成条件无法终止,也会一直在无限递归下去。最终会造成栈溢出:

图9 堆栈溢出,控台报错

只能使用 v-show

  1. 场景:在表单中,需要隐藏一些输入框,但是在表单提交的时候,需要这些值:
javascript 复制代码
/* form组件 */
<template>
  <div>
    <form ref="form" action="/" method='get'>
      <div v-show="type!='edit'">
        <label for="username">用户名:</label>
        <input type="text" name="username" v-model="username">
      </div>
      <div>
        <label for="password">密码:</label>
        <input type="text" name="password" v-model="password">
      </div>
      <button @click="submit">submit</button>
    </form>
  </div>
</template>
<script>
export default {
  data() {
    return {
      username: this.savedName,
      password: ''
    };
  },
  name: "VForm",
  props: ["type",'savedName'],
};
</script>
xml 复制代码
<template >
  <div>
      <VForm type="edit" savedName="xiaoming"/>
  </div>
</template>
<script>
import VForm from "./form";
export default {
  components: {
    VForm
  },
};
</script>
相关推荐
雾散声声慢2 分钟前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫3 分钟前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
天农学子3 分钟前
Easyui ComboBox 数据加载完成之后过滤数据
前端·javascript·easyui
mez_Blog4 分钟前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽
爱睡D小猪6 分钟前
vue文本高亮处理
前端·javascript·vue.js
开心工作室_kaic9 分钟前
ssm102“魅力”繁峙宣传网站的设计与实现+vue(论文+源码)_kaic
前端·javascript·vue.js
放逐者-保持本心,方可放逐10 分钟前
vue3 中那些常用 靠copy 的内置函数
前端·javascript·vue.js·前端框架
IT古董10 分钟前
【前端】vue 如何完全销毁一个组件
前端·javascript·vue.js
Henry_Wu00112 分钟前
从swagger直接转 vue的api
前端·javascript·vue.js
SameX22 分钟前
初识 HarmonyOS Next 的分布式管理:设备发现与认证
前端·harmonyos