Vue3进阶主题: 从VNode分析Vue3渲染优化方向及源码解析

问题引入:为什么说Vue3的渲染速度优于Vue2

问题解释

想搞清楚这个问题, 我们就需要了解Vue2中是如何生成VNode的.

Vue2中如何生成VNode树

在 Vue2 中,VNode 的生成主要通过调用 createElement 方法实现,这个方法在运行时的 compiler 模块中被定义。在编译模版时,模版会被转换为 render 函数,而 render 函数在执行时会调用 createElement 方法生成 VNode。下面是 createElement 方法的源码实现:

源码解析

export function createElement(
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array<VNode> {
  if (isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

export function _createElement (
  context: Component,
  tag?: string | Component | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): : VNode | Array<VNode> {
  // 省略一些非关键代码

  // 如果tag是字符串,则视为普通元素,创建对应的VNode对象
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // 如果是平台保留标签,直接创建对应的VNode对象
      vnode = new VNode(
        config.parsePlatformTagName(tag),
        data,
        children,
        undefined,
        undefined,
        context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // 如果tag是组件名,则根据组件定义创建对应的VNode对象
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // 否则创建未知标签对应的VNode对象
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // 如果tag不是字符串,则视为组件选项对象,创建对应的VNode对象
    vnode = createComponent(tag, data, context, children)
  }

  // 省略一些非关键代码

  // 返回生成的VNode对象
  return vnode
}

createElement 方法接收五个参数:

  • context: 当前组件实例
  • tag: VNode 节点的标签名
  • data: VNode 节点的数据,例如节点的属性和事件等
  • children: VNode 节点的子节点
  • normalizationType: 是否需要对子节点进行标准化
  • alwaysNormalize: 是否总是对子节点进行标准化

通过调用 createElement 方法,我们可以创建一个 VNode 对象,VNode 对象用于表示一个虚拟的节点,包含了该节点的标签名、属性、子节点等信息。

从上面的代码可以看出,在Vue2中,createElement函数内部并不直接使用AST,而是根据传入的参数动态创建VNode对象。创建DOM需要遍历VNode树,通过递归调用createElement函数实现的

Vue3中如何生成VNode树

在 Vue3 中,生成 VNode 树的过程是通过解析模板文件得到 AST,再通过 AST 生成 VNode 树。这一过程主要是由 compiler-core 模块中的 baseCompile 函数完成的。

源码解析

// packages/compiler-core/src/compile.ts

import { baseParse } from './parse'
import { transform } from './transform'
import { generate } from './codegen'
import { RootNode, NodeTypes } from './ast'
import { CompilerOptions } from './options'

export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  // 1. parse 阶段,将模板转换为 AST
  const ast = isString(template) ? baseParse(template, options) : template

  // 2. transform 阶段,对 AST 进行静态分析和优化
  transform(
    ast,
    extend({}, options, {
      prefixIdentifiers,
      nodeTransforms: [ // 用于处理模板中的节点
        ...nodeTransforms,
        ...(options.nodeTransforms || []) // user transforms
      ],
      directiveTransforms: extend( // 用于处理模板中的指令
        {},
        directiveTransforms,
        options.directiveTransforms || {} // user transforms
      )
    })
  )

  // 3. generate 阶段,将 AST 转换为可执行的代码
  return generate(
    ast,
    extend({}, options, {
      prefixIdentifiers
    })
  )
}

Vue3中, 废弃了createElement方法,使用compile方法对template进行编译,虽然在生成render函数前多了AST的解析的步骤,但通过这一步骤完善了VNode的各项属性,使得无需遍历VNode树即可生成DOM元素。

性能对比

我们可以设计一个相同结构的template用于验证Vue2和Vue3的性能差异, 测试代码如下:

// Vue2
const vm = new Vue({
    el: '#app',
    render(h) {
        const vNodes = []
        for (let i = 0; i < 10000; i++) {
            vNodes.push(h('div', {
                attrs: {
                    class: 'wrapper'
                }
            }, [
                h('h1', 'Hello, world!'),
                h('p', 'This is a performance test.')
            ]))
        }
        return h('div', vNodes)
    }
})

// Vue3
const { createApp, createVNode } = Vue
const app = createApp({
    render() {
        const template = createVNode('div', {
            class: 'wrapper'
        }, [
            createVNode('h1', null, 'Hello, world!'),
            createVNode('p', null, 'This is a performance test.')
        ])


        const elements = []
        for (let i = 0; i < 10000; i++) {
            elements.push(template)
        }

        return createVNode('div', null, elements)
    }
})

app.mount('#app')

下面是Chrome Devtools – Performance截图

Vue2

Vue3进阶主题: 从VNode分析Vue3渲染优化方向及源码解析

Vue3

Vue3进阶主题: 从VNode分析Vue3渲染优化方向及源码解析

测试结果显示,使用Vue3中的AST生成VNode性能更优。

总结

通过重构的解析器,Vue3在处理复杂、庞大的template时,可以更加快速的生成、渲染DOM。

参考链接

原文链接:https://juejin.cn/post/7216965516878561336 作者:没空铲屎的艾伦

(0)
上一篇 2023年4月2日 下午5:07
下一篇 2023年4月3日 上午10:00

相关推荐

发表回复

登录后才能评论