
🌈个人主页:前端青山
🔥系列专栏:Vue篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来vue篇专栏内容:v-if与v-show
目录
我们都知道在 vue 中 v-show 与 v-if 的作用效果是相同的(不含v-else),都能控制元素在页面是否显示
在用法上也是相同的
- <Model v-show="isShow" />
- <Model v-if="isShow" />
当表达式为true的时候,都会占据页面的位置
当表达式都为false时,都不会占据页面位置
控制手段不同
编译过程不同
编译条件不同
控制手段:v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在。v-if显示隐藏是将dom元素整个添加或删除
编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换
编译条件:v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染
v-show 由false变为true的时候不会触发组件的生命周期
v-if由false变为true的时候,触发组件的beforeCreate、create、beforeMount、mounted钩子,由true变为false的时候触发组件的beforeDestory、destoryed方法
性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
具体解析流程这里不展开讲,大致流程如下
将模板template转为ast结构的JS对象
用ast得到的JS对象拼装render和staticRenderFns函数
render和staticRenderFns函数被调用后生成虚拟VNODE节点,该节点包含创建DOM节点所需信息
vm.patch函数通过虚拟DOM算法利用VNODE节点创建真实DOM节点
不管初始条件是什么,元素总是会被渲染
我们看一下在vue中是如何实现的
代码很好理解,有transition就执行transition,没有就直接设置display属性
-
- export const vShow: ObjectDirective<VShowElement> = {
- beforeMount(el, { value }, { transition }) {
- el._vod = el.style.display === 'none' ? '' : el.style.display
- if (transition && value) {
- transition.beforeEnter(el)
- } else {
- setDisplay(el, value)
- }
- },
- mounted(el, { value }, { transition }) {
- if (transition && value) {
- transition.enter(el)
- }
- },
- updated(el, { value, oldValue }, { transition }) {
- // ...
- },
- beforeUnmount(el, { value }) {
- setDisplay(el, value)
- }
- }
v-if在实现上比v-show要复杂的多,因为还有else else-if 等条件需要处理,这里我们也只摘抄源码中处理 v-if 的一小部分
返回一个node节点,render函数通过表达式的值来决定是否生成DOM
-
- export const transformIf = createStructuralDirectiveTransform(
- /^(if|else|else-if)$/,
- (node, dir, context) => {
- return processIf(node, dir, context, (ifNode, branch, isRoot) => {
- // ...
- return () => {
- if (isRoot) {
- ifNode.codegenNode = createCodegenNodeForBranch(
- branch,
- key,
- context
- ) as IfConditionalExpression
- } else {
- // attach this branch's codegen node to the v-if root.
- const parentCondition = getParentCondition(ifNode.codegenNode!)
- parentCondition.alternate = createCodegenNodeForBranch(
- branch,
- key + ifNode.branches.length - 1,
- context
- )
- }
- }
- })
- }
- )
v-if 与 v-show 都能控制dom元素在页面的显示
v-if 相比 v-show 开销更大的(直接操作dom节点增加与删除)
如果需要非常频繁地切换,则使用 v-show 较好
如果在运行时条件很少改变,则使用 v-if 较好
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染
v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组或者对象,而 item 则是被迭代的数组元素的别名
在 v-for 的时候,建议设置key值,并且保证每个key值是独一无二的,这便于diff算法进行优化
两者在用法上
- <Modal v-if="isShow" />
-
- for="item in items" :key="item.id">
- {{ item.label }}
v-if与v-for都是vue模板系统中的指令
在vue模板编译的时候,会将指令系统转化成可执行的render函数
编写一个p标签,同时使用v-if与 v-for
- "app">
- <p v-if="isShow" v-for="item in items">
- {{ item.title }}
- p>
创建vue实例,存放isShow与items数据
- const app = new Vue({
- el: "#app",
- data() {
- return {
- items: [
- { title: "foo" },
- { title: "baz" }]
- }
- },
- computed: {
- isShow() {
- return this.items && this.items.length > 0
- }
- }
- })
模板指令的代码都会生成在render函数中,通过app.$options.render就能得到渲染函数
- ƒ anonymous() {
- with (this) { return
- _c('div', { attrs: { "id": "app" } },
- _l((items), function (item)
- { return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e() }), 0) }
- }
_l是vue的列表渲染函数,函数内部都会进行一次if判断
初步得到结论:v-for优先级是比v-if高
再将v-for与v-if置于不同标签
- "app">
- <template v-if="isShow">
- <p v-for="item in items">{{item.title}}p>
- template>
再输出下render函数
- ƒ anonymous() {
- with(this){return
- _c('div',{attrs:{"id":"app"}},
- [(isShow)?[_v("\n"),
- _l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}
- }
这时候我们可以看到,v-for与v-if作用在不同标签时候,是先进行判断,再进行列表的渲染
我们再在查看下vue源码
源码位置:\vue-dev\src\compiler\codegen\index.js
- export function genElement (el: ASTElement, state: CodegenState): string {
- if (el.parent) {
- el.pre = el.pre || el.parent.pre
- }
- if (el.staticRoot && !el.staticProcessed) {
- return genStatic(el, state)
- } else if (el.once && !el.onceProcessed) {
- return genOnce(el, state)
- } else if (el.for && !el.forProcessed) {
- return genFor(el, state)
- } else if (el.if && !el.ifProcessed) {
- return genIf(el, state)
- } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
- return genChildren(el, state) || 'void 0'
- } else if (el.tag === 'slot') {
- return genSlot(el, state)
- } else {
- // component or element
- ...
- }
在进行if判断的时候,v-for是比v-if先进行判断
最终结论:v-for优先级比v-if高
永远不要把 v-if 和 v-for 同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)
如果避免出现这种情况,则在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环
- if="isShow">
- <p v-for="item in items">
- template>
如果条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项
- computed: {
- items: function() {
- return this.list.filter(function (item) {
- return item.isShow
- })
- }
- }