Из исходного кода, почему нельзя использовать v-if и v-for вместе?

Vue.js

описание проблемы

В разработке мы можем написать следующий код

<!-- html模版 -->
<div id="app">
  <ul>
    <li v-for="item in list" v-if="item.age<30">
      <span>{{item.name}}</span>
      <span>{{item.age}}</span>
    </li>
  </ul>
</div>
// 列表数据
list: [
  {
    name: 'jack',
    age: 23
  },
  {
    name: 'john',
    age: 33
  },
  {
    name: 'petty',
    age: 20
  },
]

Эта операция кажется очень простой, это фильтрация отображаемого списка, но официал не рекомендует так писать.Официальная ссылка. Чиновники назвали две причины:

  1. v-for имеет более высокий приоритет, чем v-if, когда Vue обрабатывает директивы
  2. Даже если мы визуализируем элементы только для небольшого подмножества пользователей, мы должны перебирать весь список при каждом повторном рендеринге, независимо от того, изменился ли активный пользователь.

Если вам лень читать исходный текст, то можете взглянуть на скриншот ниже:

pic1
pic2

анализ проблемы

Проблема 1: v-for имеет более высокий приоритет, чем v-if, когда Vue обрабатывает директивы

Из приведенного выше описания я, наверное, понял, а. . . Но до сих пор не знаю, почему.
Например, на официальном сайте написано, что v-for имеет более высокий приоритет, чем v-if, почему? Вы говорите, что приоритет есть приоритет? 🤔
Мы можем провести простой небольшой эксперимент, который состоит в том, чтобы напечатать функцию рендеринга и посмотреть, как vue анализирует эти две инструкции.

// 打印出来的render函数
(function anonymous() {
    with (this) {
        return _c('div', {
            attrs: {
                "id": "app"
            }
        }, [_c('ul', _l((list), function(item) {
            return (item.age < 30) ? _c('li', [_c('span', [_v(_s(item.name))]), _v(" "), _c('span', [_v(_s(item.age))])]) : _e()
        }), 0)])
    }
})

Глядя на этот код напрямую, вы можете не знать, что означает название каждой функции.Мы открываем исходный код, и вы можете видеть, что вrenderMixinПри передаче прототипа vue в следующий метод

// renderMixin方法执行时注册渲染快捷方法,全部挂载在vue原型上
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)

export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys
  target._p = prependModifier
}

// _c方法在render.js中定义,表示createElement
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

Благодаря приведенным выше отношениям сопоставления функций мы можем знать, что vue проходит через_lФункция (renderList) проходит по списку, а затем обрабатывает инструкцию v-if через оператор trinocular внутри функции.Если условие истинно, создайте li и дочерние узлы, в противном случае выполните_e(createEmptyVNode) создает пустой узел, который на самом деле является узлом комментариев без текста.

// createEmptyVNode创建一个默认为空文本的注释节点
export const createEmptyVNode = (text: string = '') => {
  const node = new VNode()
  node.text = text
  node.isComment = true
  return node
}

Мы можем видеть этот пустой узел комментария в консоли. Сравнивая данные списка, мы можем узнать, что этот узел комментария является единственным.age>30изitem

pic3

Благодаря этому небольшому эксперименту мы смогли узнать, что v-for имеет более высокий приоритет, чем v-if, но вы можете спросить, почему у вас такая функция рендеринга? 😂

Затем давайте продолжим изучение функции создания функции рендеринга.
В конечном итоге мыcompilerКомпилятор шаблонов нашел ответ

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult {
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
  // 我们的render函数就是在这里生成的,里面的code通过下面的genElement方法生成
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}

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)
    // 这里就是问题的核心,先处理了v-for
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
    // 然后再处理v-if
  } 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
    let code
    if (el.component) {
      code = genComponent(el.component, el, state)
    } else {
      let data
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        data = genData(el, state)
      }

      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      code = `_c('${el.tag}'${
        data ? `,${data}` : '' // data
      }${
        children ? `,${children}` : '' // children
      })`
    }
    // module transforms
    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }
    return code
  }
}

На данный момент первый вопрос, который сказал чиновник, проанализирован, так что же означает второй пункт?

Проблема 2. Даже если мы визуализируем элементы только для небольшого подмножества пользователей, мы должны перебирать весь список при каждом повторном рендеринге, независимо от того, изменился ли активный пользователь.

Почему вы говорите, что вам нужно проходить весь список каждый раз при повторном рендеринге? На самом деле это так, после выполнения функции рендеринга будет сгенерирован vnode, представляющий собой виртуальный дом. Всякий раз, когда данные изменяются, наблюдатель будет запущен для выполнения метода обновления, а метод рендеринга будет повторно выполнен для создания нового vnode, поэтому необходимо снова просмотреть данные.

// 每个组件初始化挂载时(mountComponent)会定义一个渲染watcher
new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */
)
  // 每当组件数据变化时,就会执行这个方法
  updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }

И последнее предложение, я не знаю, заметили ли вы егоИзменились ли активные пользователи. Вы можете спросить, нужно ли мне повторно просматривать данные моего списка, если они не изменились?
Да, в vue2 для оптимизации производительности детализация наблюдателя увеличена, и он становится компонентом и наблюдателем (кроме определяемых пользователем наблюдателей), таким образом, изменения данных могут быть уведомлены только на уровне компонента. , Что касается компонента, в конце концов, какие данные были изменены и какой узел должен быть обновлен, можно узнать только путем сравнения различий виртуальных узлов vnode, сгенерированных новыми и старыми данными.

В заключение

К этому моменту все должны были ясно понять благие намерения этого документа. В реальной разработке мы должны стараться избегать такого способа написания. Если вы хотите отфильтровать данные, вы можете использовать вычисляемые свойства для фильтрации, а затем передавать их в vue для рендеринга, чтобы максимально повысить производительность.