Skip to content

Vue 3 编译器原理深入

模板编译流程、静态提升、Patch Flag、Block Tree 优化详解

什么是 Vue 编译器?

定义:将模板字符串转换为渲染函数的编译系统。

核心作用

  • 模板解析:将 HTML 模板转为 AST(抽象语法树)
  • 优化转换:静态分析和编译时优化
  • 代码生成:生成高效的渲染函数代码

编译时机

  • 构建时编译:使用 Vite/Webpack 插件(推荐)
  • 运行时编译:使用 vue/dist/vue.esm-bundler.js(体积更大)

一、编译流程概览

1.1 三个核心阶段

typescript
function compile(template: string, options?: CompilerOptions) {
  // 1. Parse - 解析模板为 AST
  const ast = parse(template, options)
  
  // 2. Transform - 转换和优化 AST
  transform(ast, options)
  
  // 3. Generate - 生成渲染函数代码
  const code = generate(ast, options)
  
  return code
}

流程图

模板字符串

【Parse 阶段】

  AST 树

【Transform 阶段】

优化后的 AST

【Generate 阶段】

渲染函数代码

1.2 示例演示

输入模板

vue
<template>
  <div id="app">
    <h1>{{ title }}</h1>
    <p>Static text</p>
    <button @click="increment">{{ count }}</button>
  </div>
</template>

输出渲染函数

javascript
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p", null, "Static text", -1)

export function render(_ctx, _cache) {
  return (_openBlock(), _createElementBlock("div", _hoisted_1, [
    _createElementVNode("h1", null, _toDisplayString(_ctx.title), 1 /* TEXT */),
    _hoisted_2,
    _createElementVNode("button", {
      onClick: _ctx.increment
    }, _toDisplayString(_ctx.count), 9 /* TEXT, PROPS */)
  ]))
}

二、Parse 阶段 - 模板解析

2.1 词法分析(Tokenization)

将模板字符串分解为 tokens:

typescript
enum TokenType {
  Text,           // 文本
  Tag,            // 标签
  Attribute,      // 属性
  Interpolation,  // 插值 {{ }}
  Comment,        // 注释
  CDATA
}

interface Token {
  type: TokenType
  content: string
  loc: SourceLocation
}

// 示例
const template = '<div id="app">{{ msg }}</div>'

// Tokens:
[
  { type: TokenType.Tag, content: '<div' },
  { type: TokenType.Attribute, content: 'id="app"' },
  { type: TokenType.Text, content: '>' },
  { type: TokenType.Interpolation, content: '{{ msg }}' },
  { type: TokenType.Tag, content: '</div>' }
]

2.2 语法分析(Parsing)

将 tokens 构建为 AST:

typescript
interface ElementNode {
  type: NodeTypes.ELEMENT
  tag: string
  props: Array<AttributeNode | DirectiveNode>
  children: TemplateChildNode[]
  isSelfClosing: boolean
  codegenNode?: VNodeCall
}

interface TextNode {
  type: NodeTypes.TEXT
  content: string
}

interface InterpolationNode {
  type: NodeTypes.INTERPOLATION
  content: ExpressionNode
}

// AST 示例
{
  type: NodeTypes.ELEMENT,
  tag: 'div',
  props: [
    { type: NodeTypes.ATTRIBUTE, name: 'id', value: 'app' }
  ],
  children: [
    {
      type: NodeTypes.INTERPOLATION,
      content: {
        type: NodeTypes.SIMPLE_EXPRESSION,
        content: 'msg'
      }
    }
  ]
}

2.3 解析器实现(简化版)

typescript
function parse(template: string): RootNode {
  const context = createParserContext(template)
  const children = parseChildren(context, [])
  
  return {
    type: NodeTypes.ROOT,
    children,
    loc: getSelection(context, 0)
  }
}

function parseChildren(
  context: ParserContext,
  ancestors: ElementNode[]
): TemplateChildNode[] {
  const nodes: TemplateChildNode[] = []
  
  while (!isEnd(context, ancestors)) {
    const s = context.source
    let node: TemplateChildNode | undefined
    
    if (s.startsWith('{{')) {
      // 解析插值
      node = parseInterpolation(context)
    } else if (s[0] === '<') {
      if (s[1] === '/') {
        // 结束标签
        parseTag(context, TagType.End)
        continue
      } else if (/[a-z]/i.test(s[1])) {
        // 元素节点
        node = parseElement(context, ancestors)
      }
    }
    
    if (!node) {
      // 文本节点
      node = parseText(context)
    }
    
    nodes.push(node)
  }
  
  return nodes
}

function parseElement(
  context: ParserContext,
  ancestors: ElementNode[]
): ElementNode {
  // 解析开始标签
  const element = parseTag(context, TagType.Start)
  
  // 自闭合标签
  if (element.isSelfClosing) {
    return element
  }
  
  // 递归解析子节点
  ancestors.push(element)
  element.children = parseChildren(context, ancestors)
  ancestors.pop()
  
  // 解析结束标签
  parseTag(context, TagType.End)
  
  return element
}

三、Transform 阶段 - AST 转换优化

3.1 转换流程

typescript
function transform(root: RootNode, options: TransformOptions) {
  const context = createTransformContext(root, options)
  
  // 遍历 AST,应用转换插件
  traverseNode(root, context)
  
  // 创建根代码生成节点
  createRootCodegen(root, context)
  
  // 完成转换
  root.helpers = [...context.helpers.keys()]
  root.components = [...context.components]
  root.directives = [...context.directives]
}

function traverseNode(
  node: RootNode | TemplateChildNode,
  context: TransformContext
) {
  // 应用节点转换
  const { nodeTransforms } = context
  const exitFns: Array<() => void> = []
  
  for (let i = 0; i < nodeTransforms.length; i++) {
    const onExit = nodeTransforms[i](node, context)
    if (onExit) {
      exitFns.push(onExit)
    }
  }
  
  // 递归处理子节点
  switch (node.type) {
    case NodeTypes.ELEMENT:
    case NodeTypes.ROOT:
      traverseChildren(node, context)
      break
    case NodeTypes.INTERPOLATION:
      context.helper(TO_DISPLAY_STRING)
      break
  }
  
  // 执行退出函数(后序遍历)
  let i = exitFns.length
  while (i--) {
    exitFns[i]()
  }
}

3.2 核心转换插件

transformElement - 元素转换

typescript
export const transformElement: NodeTransform = (node, context) => {
  return function postTransformElement() {
    if (node.type !== NodeTypes.ELEMENT) return
    
    const { tag, props } = node
    const isComponent = !isHTMLTag(tag)
    
    // 构建 VNode 调用
    const vnodeTag = isComponent ? resolveComponentType(node, context) : `"${tag}"`
    const vnodeProps = buildProps(node, context)
    const vnodeChildren = node.children
    
    // 创建 VNode 调用节点
    node.codegenNode = createVNodeCall(
      context,
      vnodeTag,
      vnodeProps,
      vnodeChildren,
      getPatchFlag(node),
      getDynamicPropNames(node)
    )
  }
}

transformText - 文本合并

typescript
export const transformText: NodeTransform = (node, context) => {
  if (
    node.type === NodeTypes.ROOT ||
    node.type === NodeTypes.ELEMENT
  ) {
    return () => {
      const children = node.children
      let currentContainer: CompoundExpressionNode | undefined
      
      for (let i = 0; i < children.length; i++) {
        const child = children[i]
        
        if (isText(child)) {
          // 合并相邻的文本节点
          for (let j = i + 1; j < children.length; j++) {
            const next = children[j]
            
            if (isText(next)) {
              if (!currentContainer) {
                currentContainer = children[i] = {
                  type: NodeTypes.COMPOUND_EXPRESSION,
                  children: [child]
                }
              }
              currentContainer.children.push(' + ', next)
              children.splice(j, 1)
              j--
            } else {
              currentContainer = undefined
              break
            }
          }
        }
      }
    }
  }
}

四、静态提升(Static Hoisting)

4.1 什么是静态提升?

静态节点提升到渲染函数外部,避免每次渲染都重新创建。

优化前

javascript
export function render(_ctx) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("p", null, "Static text"),
    _createElementVNode("p", null, "Another static")
  ]))
}

优化后

javascript
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "Static text", -1)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p", null, "Another static", -1)

export function render(_ctx) {
  return (_openBlock(), _createElementBlock("div", null, [
    _hoisted_1,
    _hoisted_2
  ]))
}

4.2 静态提升的条件

可以提升

  • 纯静态文本节点
  • 静态属性的元素节点
  • 静态子树(所有子节点都是静态的)

不能提升

  • 包含动态绑定(:classv-bind
  • 包含插值
  • 包含指令(v-ifv-for
  • 引用了作用域变量

4.3 实现原理

typescript
function hoistStatic(root: RootNode, context: TransformContext) {
  walk(
    root,
    context,
    new Map(),
    isSingleElementRoot(root, root.children[0])
  )
}

function walk(
  node: ParentNode,
  context: TransformContext,
  resultCache: Map<TemplateChildNode, boolean>,
  doNotHoistNode: boolean = false
) {
  let hasHoistedNode = false
  
  for (let i = 0; i < node.children.length; i++) {
    const child = node.children[i]
    
    // 只处理元素和文本
    if (
      child.type === NodeTypes.ELEMENT &&
      child.tagType === ElementTypes.ELEMENT
    ) {
      // 判断是否可以静态提升
      const constantType = doNotHoistNode
        ? ConstantTypes.NOT_CONSTANT
        : getConstantType(child, context)
      
      if (constantType > ConstantTypes.NOT_CONSTANT) {
        if (constantType >= ConstantTypes.CAN_HOIST) {
          // 标记为可提升
          ;(child.codegenNode as VNodeCall).patchFlag = PatchFlags.HOISTED
          child.codegenNode = context.hoist(child.codegenNode!)
          hasHoistedNode = true
          continue
        }
      }
      
      // 递归处理子节点
      if (child.children.length > 0) {
        walk(child, context, resultCache)
      }
    }
  }
  
  return hasHoistedNode
}

五、Patch Flag 优化

5.1 什么是 Patch Flag?

定义:编译时标记,指示节点的动态部分,用于运行时快速 Diff。

枚举定义

typescript
export const enum PatchFlags {
  TEXT = 1,              // 动态文本内容
  CLASS = 1 << 1,        // 动态 class
  STYLE = 1 << 2,        // 动态 style
  PROPS = 1 << 3,        // 动态属性(除 class/style)
  FULL_PROPS = 1 << 4,   // 有动态 key 的属性
  HYDRATE_EVENTS = 1 << 5, // 事件监听器
  STABLE_FRAGMENT = 1 << 6, // 稳定的 fragment
  KEYED_FRAGMENT = 1 << 7,  // 有 key 的 fragment
  UNKEYED_FRAGMENT = 1 << 8, // 无 key 的 fragment
  NEED_PATCH = 1 << 9,   // 需要 patch
  DYNAMIC_SLOTS = 1 << 10, // 动态插槽
  HOISTED = -1,          // 静态提升
  BAIL = -2              // 退出优化
}

5.2 Patch Flag 示例

vue
<template>
  <!-- 1. 动态文本 -->
  <p>{{ msg }}</p>
  <!-- PatchFlag: 1 (TEXT) -->
  
  <!-- 2. 动态 class -->
  <div :class="className"></div>
  <!-- PatchFlag: 2 (CLASS) -->
  
  <!-- 3. 动态 style -->
  <div :style="styleObject"></div>
  <!-- PatchFlag: 4 (STYLE) -->
  
  <!-- 4. 动态属性 -->
  <div :id="dynamicId"></div>
  <!-- PatchFlag: 8 (PROPS), dynamicProps: ["id"] -->
  
  <!-- 5. 事件监听 -->
  <button @click="handleClick">Click</button>
  <!-- PatchFlag: 32 (HYDRATE_EVENTS) -->
  
  <!-- 6. 多个动态属性 -->
  <div :id="id" :class="cls">{{ text }}</div>
  <!-- PatchFlag: 9 (TEXT | PROPS), dynamicProps: ["id"] -->
</template>

生成的代码

javascript
// 动态文本
_createElementVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)

// 动态 class
_createElementVNode("div", {
  class: _ctx.className
}, null, 2 /* CLASS */)

// 动态属性
_createElementVNode("div", {
  id: _ctx.dynamicId
}, null, 8 /* PROPS */, ["id"])

5.3 运行时优化

typescript
function patchElement(
  n1: VNode,
  n2: VNode,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  optimized: boolean
) {
  const el = (n2.el = n1.el!)
  const { patchFlag, dynamicChildren, dirs } = n2
  
  // 有 patchFlag,进行靶向更新
  if (patchFlag > 0) {
    if (patchFlag & PatchFlags.FULL_PROPS) {
      // 完整 props 对比
      patchProps(el, n2, oldProps, newProps, parentComponent, isSVG)
    } else {
      // 靶向更新
      if (patchFlag & PatchFlags.CLASS) {
        if (oldProps.class !== newProps.class) {
          hostPatchProp(el, 'class', null, newProps.class, isSVG)
        }
      }
      
      if (patchFlag & PatchFlags.STYLE) {
        hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
      }
      
      if (patchFlag & PatchFlags.PROPS) {
        // 只更新动态属性
        const propsToUpdate = n2.dynamicProps!
        for (let i = 0; i < propsToUpdate.length; i++) {
          const key = propsToUpdate[i]
          hostPatchProp(el, key, oldProps[key], newProps[key], isSVG)
        }
      }
    }
    
    if (patchFlag & PatchFlags.TEXT) {
      // 只更新文本内容
      if (n1.children !== n2.children) {
        hostSetElementText(el, n2.children as string)
      }
    }
  }
  
  // 更新子节点
  if (dynamicChildren) {
    // 只更新动态子节点
    patchBlockChildren(n1.dynamicChildren!, dynamicChildren, el, ...)
  } else if (!optimized) {
    // 完整 diff
    patchChildren(n1, n2, el, ...)
  }
}

六、Block Tree 优化

6.1 什么是 Block?

定义:收集所有动态后代节点的特殊 VNode,用于跳过静态节点的 Diff。

传统 VNode 树

div
├── p (static)
├── span (dynamic)
└── div
    ├── p (static)
    └── span (dynamic)

Block Tree

Block (div)
  dynamicChildren: [span, span]  // 只收集动态节点

6.2 Block 的创建

typescript
// 编译后的代码
export function render(_ctx) {
  return (
    _openBlock(),  // 开启 Block 收集
    _createElementBlock("div", null, [
      _createElementVNode("p", null, "Static"),
      _createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
      _createElementVNode("div", null, [
        _createElementVNode("p", null, "Static"),
        _createElementVNode("span", null, _toDisplayString(_ctx.count), 1 /* TEXT */)
      ])
    ])
  )
}

运行时实现

typescript
let currentBlock: VNode[] | null = null
const blockStack: VNode[][] = []

export function openBlock(disableTracking = false) {
  blockStack.push((currentBlock = disableTracking ? null : []))
}

export function closeBlock() {
  blockStack.pop()
  currentBlock = blockStack[blockStack.length - 1] || null
}

export function createElementBlock(
  type: string | Component,
  props?: Record<string, any> | null,
  children?: any,
  patchFlag?: number,
  dynamicProps?: string[]
): VNode {
  return setupBlock(
    createBaseVNode(type, props, children, patchFlag, dynamicProps)
  )
}

function setupBlock(vnode: VNode) {
  // 将收集到的动态子节点附加到 vnode
  vnode.dynamicChildren = currentBlock || EMPTY_ARR
  closeBlock()
  
  // 当前 vnode 也可能是动态的,添加到父 Block
  if (currentBlock) {
    currentBlock.push(vnode)
  }
  
  return vnode
}

// 创建 VNode 时自动收集
export function createVNode(
  type: VNodeTypes,
  props?: (Data & VNodeProps) | null,
  children?: unknown,
  patchFlag?: number
): VNode {
  const vnode = createBaseVNode(type, props, children, patchFlag)
  
  // 如果有 patchFlag,添加到当前 Block
  if (patchFlag > 0 && currentBlock) {
    currentBlock.push(vnode)
  }
  
  return vnode
}

6.3 Block Tree 的 Diff 优化

typescript
function patchBlockChildren(
  oldChildren: VNode[],
  newChildren: VNode[],
  fallbackContainer: RendererElement,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean
) {
  // 只对比动态子节点,跳过静态节点
  for (let i = 0; i < newChildren.length; i++) {
    const oldVNode = oldChildren[i]
    const newVNode = newChildren[i]
    
    // 确定容器
    const container =
      oldVNode.el &&
      (oldVNode.type === Fragment ||
       !isSameVNodeType(oldVNode, newVNode))
        ? hostParentNode(oldVNode.el)!
        : fallbackContainer
    
    // 直接 patch,不需要遍历整棵树
    patch(
      oldVNode,
      newVNode,
      container,
      null,
      parentComponent,
      parentSuspense,
      isSVG,
      true  // optimized
    )
  }
}

6.4 性能对比

传统 Diff

  • 需要遍历整棵树
  • 对比所有节点(包括静态节点)
  • 时间复杂度:O(n)

Block Tree Diff

  • 只遍历动态节点
  • 跳过静态节点
  • 时间复杂度:O(动态节点数)

示例

vue
<template>
  <div>
    <p>Static 1</p>
    <p>Static 2</p>
    <p>Static 3</p>
    <span>{{ msg }}</span>  <!-- 唯一的动态节点 -->
    <p>Static 4</p>
    <p>Static 5</p>
  </div>
</template>
  • 传统 Diff:需要对比 7 个节点
  • Block Tree:只对比 1 个动态节点(span)

七、Generate 阶段 - 代码生成

7.1 代码生成流程

typescript
export function generate(
  ast: RootNode,
  options: CodegenOptions = {}
): CodegenResult {
  const context = createCodegenContext(ast, options)
  const { push, indent, deindent, newline } = context
  
  // 生成函数前导码
  genFunctionPreamble(ast, context)
  
  // 生成渲染函数
  const functionName = `render`
  const args = ['_ctx', '_cache']
  const signature = args.join(', ')
  
  push(`function ${functionName}(${signature}) {`)
  indent()
  
  // 生成函数体
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context)
  } else {
    push(`return null`)
  }
  
  deindent()
  push(`}`)
  
  return {
    ast,
    code: context.code,
    preamble: '',
    map: context.map ? context.map.toJSON() : undefined
  }
}

7.2 节点代码生成

typescript
function genNode(node: CodegenNode, context: CodegenContext) {
  switch (node.type) {
    case NodeTypes.ELEMENT:
    case NodeTypes.IF:
    case NodeTypes.FOR:
      genNode(node.codegenNode!, context)
      break
    case NodeTypes.TEXT:
      genText(node, context)
      break
    case NodeTypes.SIMPLE_EXPRESSION:
      genExpression(node, context)
      break
    case NodeTypes.INTERPOLATION:
      genInterpolation(node, context)
      break
    case NodeTypes.COMPOUND_EXPRESSION:
      genCompoundExpression(node, context)
      break
    case NodeTypes.VNODE_CALL:
      genVNodeCall(node, context)
      break
    case NodeTypes.JS_CALL_EXPRESSION:
      genCallExpression(node, context)
      break
  }
}

function genVNodeCall(node: VNodeCall, context: CodegenContext) {
  const { push, helper } = context
  const { tag, props, children, patchFlag, dynamicProps } = node
  
  // 生成 createVNode 调用
  push(helper(CREATE_ELEMENT_VNODE) + `(`)
  
  // 生成参数
  const args = [tag, props, children]
  
  if (patchFlag) {
    args.push(patchFlag + '')
  }
  
  if (dynamicProps) {
    args.push(dynamicProps)
  }
  
  genNodeList(args, context)
  push(`)`)
}

7.3 完整示例

输入模板

vue
<div id="app" :class="className">
  <p>Static</p>
  <span>{{ msg }}</span>
</div>

生成的代码

javascript
import { 
  createElementVNode as _createElementVNode,
  toDisplayString as _toDisplayString,
  normalizeClass as _normalizeClass,
  openBlock as _openBlock,
  createElementBlock as _createElementBlock
} from "vue"

const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p", null, "Static", -1)

export function render(_ctx, _cache) {
  return (_openBlock(), _createElementBlock("div", {
    id: "app",
    class: _normalizeClass(_ctx.className)
  }, [
    _hoisted_2,
    _createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ], 2 /* CLASS */))
}

八、编译优化总结

8.1 优化对比

优化技术作用性能提升
静态提升避免重复创建静态节点减少内存分配
Patch Flag靶向更新动态内容减少 Diff 时间
Block Tree跳过静态节点 Diff大幅减少遍历
事件缓存缓存事件处理函数避免重复创建

8.2 Vue 2 vs Vue 3 编译对比

Vue 2

  • 无编译时优化
  • 运行时全量 Diff
  • 无静态标记

Vue 3

  • 静态提升
  • Patch Flag 靶向更新
  • Block Tree 跳过静态节点
  • 性能提升 1.3-2 倍

面试高频题

1: Vue 3 编译器的三个阶段是什么?

答案

  1. Parse(解析):将模板字符串解析为 AST
  2. Transform(转换):遍历 AST,应用优化转换
  3. Generate(生成):将优化后的 AST 生成渲染函数代码

2: 什么是静态提升?有什么好处?

答案

定义:将静态节点提升到渲染函数外部,避免每次渲染都重新创建。

好处

  1. 减少内存分配
  2. 减少 GC 压力
  3. 提升渲染性能

示例

javascript
// 提升前
function render() {
  return h('div', [
    h('p', 'Static')  // 每次都创建
  ])
}

// 提升后
const _hoisted = h('p', 'Static')  // 只创建一次
function render() {
  return h('div', [_hoisted])
}

3: Patch Flag 是什么?如何工作?

答案

定义:编译时标记,指示节点的动态部分。

工作原理

  1. 编译时分析节点,标记动态内容类型
  2. 运行时根据 flag 进行靶向更新,跳过静态部分

常见 Flag

  • 1:TEXT(动态文本)
  • 2:CLASS(动态 class)
  • 4:STYLE(动态 style)
  • 8:PROPS(动态属性)

4: Block Tree 是什么?解决了什么问题?

答案

定义:收集所有动态后代节点的特殊 VNode。

解决的问题

  • 传统 Diff 需要遍历整棵树
  • 大量静态节点浪费性能

优化效果

  • 只对比动态节点
  • 跳过静态节点
  • 性能提升显著(特别是静态内容多的场景)

5: Vue 3 编译器如何优化 v-for?

答案

  1. Fragment 优化:使用 Fragment 避免额外包裹元素
  2. Patch Flag:标记为 KEYED_FRAGMENTUNKEYED_FRAGMENT
  3. Block 收集:每个列表项是一个 Block
  4. 稳定性检测:检测列表是否稳定(顺序不变)
javascript
// 有 key
_createElementBlock(Fragment, null, [
  (_openBlock(true), _createElementBlock(Fragment, null, 
    _renderList(_ctx.list, (item) => {
      return (_openBlock(), _createElementBlock("div", { key: item.id }))
    }),
    128 /* KEYED_FRAGMENT */
  ))
])

6: 编译时优化和运行时优化的区别?

答案

编译时优化(Vue 3 重点):

  • 静态提升
  • Patch Flag 标记
  • Block Tree 收集
  • 发生在构建阶段
  • 无运行时开销

运行时优化

  • 虚拟 DOM Diff
  • 组件缓存(keep-alive)
  • 异步组件
  • 发生在浏览器中
  • 有运行时开销

7: 什么情况下节点不能被静态提升?

答案

不能提升的情况

  1. 包含动态绑定(:classv-bind
  2. 包含插值
  3. 包含指令(v-ifv-forv-show
  4. 引用了组件状态或 props
  5. 使用了 refv-once

8: Vue 3 如何优化事件监听器?

答案

事件缓存

javascript
// 优化前
function render(_ctx) {
  return h('button', {
    onClick: () => _ctx.count++  // 每次都创建新函数
  })
}

// 优化后
function render(_ctx, _cache) {
  return h('button', {
    onClick: _cache[0] || (_cache[0] = $event => _ctx.count++)
  })
}

好处

  • 避免重复创建函数
  • 减少内存分配
  • 避免子组件不必要的更新

9: 如何查看编译后的代码?

答案

方法 1:Vue SFC Playground

方法 2:使用 @vue/compiler-sfc

javascript
import { compile } from '@vue/compiler-dom'

const { code } = compile(`
  <div>{{ msg }}</div>
`, {
  mode: 'module',
  hoistStatic: true
})

console.log(code)

方法 3:Vite 插件

javascript
// vite.config.js
export default {
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // 查看编译选项
        }
      }
    })
  ]
}

10: Vue 3 编译器相比 Vue 2 有哪些改进?

答案

Vue 3 改进

  1. 静态提升:Vue 2 无此优化
  2. Patch Flag:靶向更新,Vue 2 全量对比
  3. Block Tree:跳过静态节点,Vue 2 全树遍历
  4. 事件缓存:避免重复创建函数
  5. 更好的 Tree-shaking:按需引入辅助函数
  6. 更快的编译速度:优化的解析器

性能提升

  • 初始渲染:快 55%
  • 更新性能:快 133%
  • 内存使用:减少 54%