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