• 优先级比较:v-for 和 v-if哪个更高?

    结论

    1、本身并不建议将v-for和v-if同时使用的。

    2、vue2里面v-for比v-if的优先级更高。因为vue2在模板编译的时候会先处理v-for再处理v-if,所以生成的渲染函数会先执行循环,然后在循环里面再执行条件判断。

    3、这样做带来的问题就是

    对于场景1:<li v-for="user in users" v-if="user.show">

    每次重新渲染的时候,都要重新遍历整个列表,其实我们只需要列表的一部分,这样做浪费性能。推荐的做法是,通过计算属性先过滤出我们需要的部分,再去渲染,更高效。

    对于场景2: <li v-for="user in users" v-if="globalShow">

    globalShow这个判断其实如果是false,循环并不需要执行,但是现在跟v-if一起用,不管globalShow是否是true都要执行循环,完全是浪费。推荐的做法是将v-if上移到ul容器。

    4、需要注意的是,vue3的breaking change,在vue3中v-if的优先级比v-for高,所以如果同时使用的话,对于场景1,这个时候user还没有,v-if="user.show"就会报错

    5、一般我们如果有用eslint,也会给我们报错,对应的规则是:vue/no-use-v-if-with-v-for

    实际例子

    例如:以下的模板,将会生成下面的渲染函数

    <ul>
    
        <li v-for="user in users" v-if="user.isActive" :key="user.id">
    
            {{ user.name }}
    
        </li>
    
    </ul>

    生成的渲染函数如下

    with(this) {
    
        return _c('ul', _l((users), function (user) {
    
            return (user.isActive) ? _c('li', user.name) : _e()
    
        }), 0)
    
    }

    从上面生成的渲染函数可以看出,会先执行_l遍历user,在里面进行条件判断

    源码

    处理v-if和v-for的源码

    src/compiler/index.js

    // 模板解析,生成ast树
    
    const ast = parse(template.trim(), options)
    
    if (options.optimize !== false) {
    
        optimize(ast, options)
    
    }
    
    const code = generate(ast, options)

    根据ast生成代码,假如是上面的模板,生成的ast简化后如下

    // 可以看出v-for和v-if都解析出来了
    
     ast = {
    
         'type': 1,
    
         'tag': 'ul',
    
         'attrsList': [],
    
         'attrsMap': {},
    
         'children': [{
    
         'type': 1,
    
         'tag': 'li',
    
         'attrsList': [],
    
         'attrsMap': {
    
             'v-for': 'user in users',
    
             'v-if': 'user.show'
    
         },
    
         // v-if解析出来的属性
    
         'if': 'user.show',
    
         'ifConditions': [{
    
             'exp': 'user.show',
    
             'block': // 指向el自身
    
         }],
    
         // v-for解析出来的属性
    
         'for': 'users',
    
         'alias': 'user',
    
         'iterator1': 'index',
    
         'parent': // 指向其父节点
    
         'children': [
    
             'type': 2,
    
             'expression': '_s(user)'
    
             'text': '{{user}}',
    
             'tokens': [
    
                 {'@binding':'user'},
    
             ]
    
          ]
    
         }]
    
     }

    compiler/codegen/index.js

    // generate 调用 genElement
    
    const code = ast ? genElement(ast, state) : '_c("div")'
    
    // genElement里面的处理
    
    if (el.staticRoot && !el.staticProcessed) {
    
    return genStatic(el, state)
    
    } else if (el.once && !el.onceProcessed) {
    
    return genOnce(el, state)
    
    // 从这可以看出来,先执行genFor,处理v-for指令,在genFor里面会递归调用genElement,继续处理v-if,genFor会将forProcessed设为true,这样下次进来的时候就不会处理for了
    
    } 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 {
    
    // 最后这里处理标签等
    
    const children = el.inlineTemplate ? null : genChildren(el, state, true)
    
    code = `_c('${el.tag}'${
    
    data ? `,${data}` : '' // data
    
    }${
    
    children ? `,${children}` : '' // children
    
    })`
    
    }
    
     
    
    // genFor的代码
    
    const exp = el.for // 对应上面ast的 for: users
    
    const alias = el.alias // alias: user
    
    // iterator1 对应v-for的(item,key,index) in items的key
    
    // iterator2 对应的是index
    
    // 通常我们遍历数组 key就是index
    
    // 假如我们遍历的是对象 key就是对象的key,index就是遍历的索引
    
    const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
    
    const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
    
    el.forProcessed = true // 下次递归调用genElement的时候就不会重复处理v-for了
    
    return `${altHelper || '_l'}((${exp}),` +
    
    `function(${alias}${iterator1}${iterator2}){`
    
     
    
    // 这里处理完了v-for,递归调用genElement继续处理v-if
    
    `return ${(altGen || genElement)(el, state)}` +
    
    '})'
    
    最终会生成类似如下的代码返回出去
    
    
    
    
    
    _l((users), function(user, index) {
    
        // 如果有v-if 前面就会有个条件判断,如user.isActive
    
        return (user.isActive) ? _c('li', user.name) : _e()
    
    });
  • 发表回复