一.v-if和v-show的原理
- v-if:通过移除元素来实现元素的显示与隐藏,每次显示都会触发组件的mouted钩子函数。可以用来表面意义上的刷新组件
- v-show:通过设置元素的display属性来控制元素的显示与隐藏。elementui的弹窗组件就是利用v-show来控制显示与隐藏的。
二.v-if和v-show的区别
- 加载速度不同,v-show要比v-if快,实际项目中就是这样。
- 原理不同。
- 应用场景不同,v-if多用于需要刷新的组件。v-show多用于不需要刷新组件的显示与隐藏。
- 组件内缓存子组件就是用的v-show。
- 做项目优化的时候可以尽量使用v-show,具体看需求。
三.vue源码分析v-if和v-show
在开始之前我们要知道vue2中字符串模板解析编译成真实DOM的过程,大致流程如下:
- 将模板template转为ast结构的JS对象
- 用ast得到的JS对象拼装render和staticRenderFns函数
- render和staticRenderFns函数被调用后生成虚拟VNODE节点,该节点包含创建DOM节点所需信息
- vm.patch函数通过虚拟DOM算法利用VNODE节点创建真实DOM节点
-
v-if源码分析
1.在模板编译的parse阶段,会使用 processIfConditions 函数处理条件渲染:
function processIfConditions (el, parent) {
const prev = findPrevElement(parent.children)
if (prev && prev.if) {
addIfCondition(prev, {
exp: el.elseif,
block: el
})
} else if (process.env.NODE_ENV !== 'production') {
// 警告信息
}
}
2.在模板编译的 codegen 阶段,会调用 genIf 函数处理 v-if 所在的标签:
export function genIf (
el: any,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
el.ifProcessed = true // avoid recursion
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (
conditions: ASTIfConditions,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
if (!conditions.length) {
return altEmpty || '_e()'
}
const condition = conditions.shift()
if (condition.exp) {
return `(${condition.exp})?${
genTernaryExp(condition.block)
}:${
genIfConditions(conditions, state, altGen, altEmpty)
}`
} else {
return `${genTernaryExp(condition.block)}`
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
从代码中可以看出,v-if 指令会转化成三目运算符的形式。
带有 v-if 指令的模板会编译成根据数据源真假值来调用具体辅助方法的渲染函数,v-if 会根据数据源真假值来决定是否渲染该节点,这一点与 v-show 不同。
-
v-show源码分析
在调用处理指令的钩子函数 updateDirectives 时,v-show 指令有所不同,相当于 v-show 内部实现了自定义指令的 bind、update、unbind 三个阶段的钩子函数。
export default {
bind (el, { value }, vnode) {
vnode = locateNode(vnode)
const transition = vnode.data && vnode.data.transition
const originalDisplay = el.__vOriginalDisplay =
el.style.display === 'none' ? '' : el.style.display
if (value && transition) {
vnode.data.show = true
enter(vnode, () => {
el.style.display = originalDisplay
})
} else {
el.style.display = value ? originalDisplay : 'none'
}
},
update (el, { value, oldValue }, vnode) {
if (!value === !oldValue) return
vnode = locateNode(vnode)
const transition = vnode.data && vnode.data.transition
if (transition) {
vnode.data.show = true
if (value) {
enter(vnode, () => {
el.style.display = el.__vOriginalDisplay
})
} else {
leave(vnode, () => {
el.style.display = 'none'
})
}
} else {
el.style.display = value ? el.__vOriginalDisplay : 'none'
}
},
unbind (el,binding,vnode,oldVnode,isDestroy){
if (!isDestroy) {
el.style.display = el.__vOriginalDisplay
}
}
}
从上述代码可以看到,v-show 指令仅仅是通过调用 DOM.style.display 的值来显示和隐藏DOM元素。