описание проблемы
В разработке мы можем написать следующий код
<!-- 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
},
]
Эта операция кажется очень простой, это фильтрация отображаемого списка, но официал не рекомендует так писать.Официальная ссылка. Чиновники назвали две причины:
- v-for имеет более высокий приоритет, чем v-if, когда Vue обрабатывает директивы
- Даже если мы визуализируем элементы только для небольшого подмножества пользователей, мы должны перебирать весь список при каждом повторном рендеринге, независимо от того, изменился ли активный пользователь.
Если вам лень читать исходный текст, то можете взглянуть на скриншот ниже:
анализ проблемы
Проблема 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
Благодаря этому небольшому эксперименту мы смогли узнать, что 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 для рендеринга, чтобы максимально повысить производительность.