开发过程中经常会遇到控制元素显示与隐藏,大家也都知道可以使用 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
- 场景:在实现递归组件时,必须使用 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
- 场景:在表单中,需要隐藏一些输入框,但是在表单提交的时候,需要这些值:
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>