vue中v-for和v-if可以一起使用吗?
高频面试题,
vue
中的v-if
和v-for
可以一起使用吗?
答案是:用了也能出来预期的效果,但是会有性能浪费。 看以下例子:
new Vue({
el: "#app",
data() {
return {
list: ["TEXT-a", "TEXT-b", "TEXT-c", "TEXT-d"]
};
},
template: `<ul><li v-if="index !== 1" v-for="(item, index) in list">{{item}}</li></ul>`
});
例子中,v-for
和v-if
同时使用,并且特意把v-if
放在前面,我们知道vue
从模板到视图展示出来,会经历三个主要的流程,主要有编译、获取虚拟DOM
和patch
过程。
一、编译
1、ast
生成过程
ast
的生成过程中,ul
的子元素li
上的属性有if:"index !== 1"
来描述if
属性,有alias: "item"
、iterator1: "index"
和for: "list"
的属性来描述v-for
。通过optimize
优化后,执行generate
。
2、generate
生成可执行代码字符串
generate
中主要的逻辑是genElement
:
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 {
// ...
}
}
可以看出el.for
的优先级比v-if
要高,整个code
构建过程是递归的过程,当执行完成后会得到code
码为:
"_c('ul',_l((list),function(item,index){return (index !== 1)?_c('li',[_v(_s(item))]):_e()}),0)"
可以看出,_l
方法控制循环构建vNode
主流程,返回的vNode
又由内部的index !== 1
作为条件控制,条件满足生成TEXT
类型的vNode
,条件不满足生成Comment
类型的vNode
。接下来看vNode
获取过程:
二、vNode
获取
通过编译过程,获取到的render
函数为:
with(this){
return _c('ul',_l((list),function(item,index){return (index !== 1)?_c('li',[_v(_s(item))]):_e()}),0)
}
当执行vnode = render.call(vm._renderProxy, vm.$createElement)
的时候,会执行vue
原型上挂载的函数_c
、_l
、_v
、_s
和_e
。我们主要来看_l
函数:
/**
* Runtime helper for rendering v-for lists.
*/
export function renderList (
val: any,
render: (
val: any,
keyOrIndex: string | number,
index?: number
) => VNode
): ?Array<VNode> {
let ret: ?Array<VNode>, i, l, keys, key
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length)
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i)
}
}
// val为其他类型的场景...
}
文中例子中的list
为Array
类型,所以这里ret
的长度为4
,这里的render
就是:
function(item,index){return (index !== 1)?_c('li',[_v(_s(item))]):_e()}
当满足index !== 1
会创建TEXT
类型的vNode
,当不满足时执行_e
获得空注释节点:
export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
注意这里的isComment
为true
,最终在条件不满足的时候创建的是空的注释节点。最终的vNode
列表中包含3
个以TEXT
类vNode
为子节点的li
类vNode
,1
个空注释节点:
三、patch
过程
在patch
的过程中,会执行到createElm
,进而执行到createChildren
:
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(children);
}
for (var i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
}
}
当第二次循环的时候i
为1
,此时会执行到createElm
中的:
// ...
else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
// ...
到这里发现,即使创建的空注释,也需要执行parentElm
中插入空字符的操作。整个patch
也是递归的过程,执行的是先插入子元素后插入父元素的操作,当执行到递归顶层的insert(parentElm, vnode.elm, refElm)
的时候,ul
中就插入了三个li
元素和一个注释空节点:
总结
当对list
通过过滤的方式处理后:
new Vue({
el: "#app",
data() {
return {
list: ["TEXT-a", "TEXT-b", "TEXT-c", "TEXT-d"]
};
},
created() {
this.list = this.list.filter((val, index) => index !== 1);
},
template: `<ul><li v-for="(item, index) in list">{{item}}</li></ul>`
});
- 生成的
code
少了if
的三目运算符
"_c('ul',_l((list),function(item,index){return _c('li',[_v(_s(item))])}),0)"
- 生成的
vNode
少了Comment
类型
- 生成的真实
DOM
少了注释空节点
综上所述,v-if
和v-for
同时出现的场景可以拆分为,数据在渲染之前借助生命周期进行处理,也可通过计算属性的方式进行处理。减少vNode
的生成,也减少整个渲染过程的流程,最终减少注释类的DOM
节点,以达到性能优化的目的。1
个数据不明显,那么如果有大量的数据,这种处理的收益就很可观。
后记
如有纰漏,请贵手留言~ 如有帮助,可收藏关注~
您的点赞,是我持续前行和分享的动力~
转载自:https://juejin.cn/post/7137326610822201357