Files
blog-press/docs/Web-Front/Vue/Vue3-Architecture.md
2025-12-13 16:27:56 +08:00

11 KiB
Raw Blame History

一、Vue3 整体架构图

graph TD
    A[Vue 3] --> B[Compiler]
    A --> C[Runtime]
    A --> D[Reactivity]

    B --> B1[Parser]
    B --> B2[Transformer]
    B --> B3[Codegen]

    C --> C1[Virtual DOM]
    C --> C2[Renderer]
    C --> C3[Component]

    D --> D1[Proxy]
    D --> D2[Effect]
    D --> D3[Dependency Graph]

    B3 -->|生成| C1
    C1 -->|Diff/Patch| C2
    D3 -->|驱动更新| C1

1.1 编译器模块

  编译器负责将模板转换为可执行的渲染函数,包含解析器Parser、转换器Transformer和代码生成器Codegen 三个子模块。

  • compiler-core: 核心编译器负责基础的编译功能如生成抽象语法树AST、基础的模板编译等是平台无关的编译器核心。
  • compiler-dom: 基于compiler-core针对浏览器环境的编译模块重写了编译和解析方法处理与原生 DOM API 相关的编译工作。
  • compiler-sfc: 用于处理 Vue 单文件组件(.vue的编译将单文件组件中的模板、脚本和样式进行解析和编译。
  • compiler-ssr服务端渲染的编译模块负责在服务端将 Vue 组件编译为适合服务器端渲染的代码。

1.2 运行时模块

  运行时系统处理虚拟DOM的创建、更新和渲染包含组件实例管理、虚拟DOM diff算法和平台渲染器。

  • runtime-core运行时核心模块包含了 Vue 组件的生命周期、虚拟节点vnode、观察者watch等核心 API是平台无关的运行时核心。
  • runtime-dom运行时与 DOM 相关的核心模块,提供了与浏览器 DOM 交互的功能,如创建应用实例、操作 DOM 元素等。

1.3 响应式系统

响应式系统基于Proxy实现数据劫持通过依赖收集和触发更新机制驱动视图变化。
  三大系统协同工作:编译器生成的渲染函数由运行时执行,响应式系统追踪数据变化触发组件更新,形成完整的渲染闭环。

二、基础模块

2.1 响应式系统

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px

    A(原始对象):::process -->|Proxy 包装| B(响应式对象):::process
    B -->|属性读取| C(依赖收集):::process
    B -->|属性修改| D(触发更新):::process
    C --> E(副作用函数):::process
    D --> E
    E -->|更新视图| F(真实 DOM):::process

Vue3的响应式系统通过Proxy代理对象属性在get操作时通过track函数收集当前正在执行的副作用如组件渲染函数作为依赖存储到依赖关系图中。当set操作发生时trigger函数根据依赖关系图找到所有关联的副作用函数通过调度器批量执行。
  这种设计使得数据变化能精准触发组件更新,同时通过异步批处理避免重复渲染。
响应式系统独立于其他模块可单独使用为Vue3提供了高效的状态管理能力。

2.2 虚拟 DOM

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px

    A(旧虚拟 DOM 树):::process -->|Diff 算法| B(差异比较):::process
    C(新虚拟 DOM 树):::process --> B
    B -->|更新操作| D(真实 DOM):::process

当组件更新时Vue会比较新旧虚拟DOM树的差异。通过Patch Flags快速识别动态节点类型如文本、样式等仅对标记为动态的部分进行比较对于带key的子节点列表使用最长递增子序列算法最小化DOM操作完全静态的子树则直接跳过diff过程。
这种靶向更新策略将传统虚拟DOM算法O(n³)的时间复杂度优化到接近O(n),同时配合异步批量更新机制,确保复杂应用也能保持流畅渲染

2.3 编译器

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px

    A(模板字符串):::process -->|解析| B(抽象语法树 - AST):::process
    B -->|转换| C(优化后的 AST):::process
    C -->|生成| D(渲染函数代码):::process

Vue编译器将模板字符串转换为渲染函数经历解析、转换和代码生成三个阶段。
解析阶段通过词法分析生成原始AST抽象语法树转换阶段对AST进行优化包括静态节点提升将不变节点提取到渲染函数外部、补丁标记给动态节点添加优化标识、树结构打平减少动态子节点的嵌套层级最后代码生成阶段输出优化的渲染函数。
  这些优化使得运行时能跳过静态内容比较,直接定位动态节点,大幅提升渲染性能。

2.4 渲染器

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px

    A(虚拟 DOM 树):::process -->|创建节点| B(真实 DOM 节点):::process
    B -->|插入节点| C(页面):::process
    A -->|更新节点| B

Vue渲染器是连接虚拟DOM与真实平台的桥梁分为虚拟DOM处理层、Patch算法层和平台操作层。
它通过抽象平台API如createElement、insert等实现跨平台能力开发者可配置不同平台的底层操作来支持Web、Canvas甚至原生应用。
渲染器内部采用增量更新策略结合编译器提供的优化提示如静态标记、动态子节点索引确保DOM操作最精简。
这种设计使Vue在保证Web端高性能的同时也能灵活适配各种渲染环境。

三、Vue3应用生命周期

3.1 初始化阶段

  流程:

  1. 调用createApp()创建应用实例
  2. 创建平台专属渲染器DOM/SSR/Test
  3. 初始化应用上下文和插件系统

  关键代码:

// runtime-dom/index.ts
const renderer = createRenderer({
  patchProp,  // DOM属性操作
  ...nodeOps  // DOM节点操作集合
})

function createApp(rootComponent) {
  const context = createAppContext()
  const app = {
    _component: rootComponent,
    mount(container) {
      const vnode = createVNode(rootComponent)
      renderer.render(vnode, container)
      return vnode.component.proxy
    }
  }
  return app
}

3.2 组件挂载阶段

  流程:

  1. 标准化容器(字符串选择器 → DOM元素
  2. 创建根组件虚拟节点vnode
  3. 初始化组件解析props和slots、执行setup函数、建立响应式连接等
  4. 建立渲染effect为组件创建响应式的副作用函数

  关键代码:

// runtime-core/renderer.ts
function mountComponent(vnode, container) {
  // 1. 创建组件实例
  const instance = createComponentInstance(vnode)
  
  // 2. 初始化组件
  setupComponent(instance)
  
  // 3. 建立渲染effect
  setupRenderEffect(instance, container)
}

function setupRenderEffect(instance, container) {
  instance.update = effect(() => {
    if (!instance.isMounted) {
      // 首次渲染
      const subTree = (instance.subTree = renderComponentRoot(instance))
      patch(null, subTree, container)
      instance.isMounted = true
    } else {
      // 更新渲染
      const nextTree = renderComponentRoot(instance)
      patch(instance.subTree, nextTree, container)
      instance.subTree = nextTree
    }
  }, { scheduler: queueJob })
}

3.3 响应式更新阶段

  流程:

  1. 数据变更触发Proxy的set拦截
  2. 通过trigger触发依赖的effect
  3. 调度器将更新任务加入队列
  4. 下一个tick执行队列中的任务
  5. 执行组件render函数生成新vnode
  6. 调用patch进行差异更新

  关键代码:

// reactivity/reactive.ts
function createReactiveObject(target) {
  return new Proxy(target, {
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      if (hasChanged(value, oldValue)) {
        trigger(target, key)  // 触发更新
      }
      return result
    }
  })
}

// runtime-core/scheduler.ts
const queue = []
function queueJob(job) {
  if (!queue.includes(job)) {
    queue.push(job)
    queueFlush()
  }
}

function queueFlush() {
  if (!isFlushing) {
    nextTick(flushJobs)
  }
}

function flushJobs() {
  isFlushing = true
  queue.sort((a, b) => a.id - b.id)  // 保证父组件先更新
  for (let i = 0; i < queue.length; i++) {
    queue[i]()
  }
  queue.length = 0
  isFlushing = false
}

3.4 虚拟DOM Patch阶段

  流程:

  1. 比较新旧vnode的类型
  2. 不同类型:卸载旧节点,挂载新节点
  3. 相同类型:更新节点
  4. 子节点对比采用高效diff算法

  关键代码:

// runtime-core/renderer.ts
function patch(n1, n2, container) {
  // 1. 类型不同直接卸载
  if (n1 && !isSameVNodeType(n1, n2)) {
    unmount(n1)
    n1 = null
  }

  const { type, patchFlag } = n2
  
  // 2. 根据类型处理
  switch (type) {
    case Text:
      processText(n1, n2, container)
      break
    case Fragment:
      processFragment(n1, n2, container)
      break
    default:
      if (shapeFlag & ShapeFlags.ELEMENT) {
        processElement(n1, n2, container)
      } else if (shapeFlag & ShapeFlags.COMPONENT) {
        processComponent(n1, n2, container)
      }
  }
}

function processElement(n1, n2, container) {
  if (n1 == null) {
    mountElement(n2, container)
  } else {
    patchElement(n1, n2)
  }
}

function patchElement(n1, n2) {
  // 根据patchFlag进行优化更新
  if (n2.patchFlag & PatchFlags.FULL_PROPS) {
    // 全量props更新
  } else {
    // 只更新动态props
  }
  
  // 更新子节点
  patchChildren(n1, n2)
}

3.5 组件卸载阶段

  流程:

  1. 触发beforeUnmount生命周期钩子
  2. 停止组件渲染effect
  3. 递归卸载子组件
  4. 移除DOM节点
  5. 触发unmounted生命周期钩子
  6. 清理响应式依赖

  关键代码:

// runtime-core/renderer.ts
function unmount(vnode) {
  if (vnode.type === Fragment) {
    unmountChildren(vnode.children)
    return
  }

  const { component } = vnode
  if (component) {
    unmountComponent(component)
  } else {
    remove(vnode.el!)
  }
}

function unmountComponent(instance) {
  // 1. 触发beforeUnmount
  if (instance.bum) {
    invokeArrayFns(instance.bum)
  }
  
  // 2. 停止响应式effect
  stop(instance.update)
  
  // 3. 递归卸载子树
  unmount(instance.subTree)
  
  // 4. 触发unmounted
  if (instance.um) {
    queuePostRenderEffect(instance.um)
  }
  
  // 5. 清理引用
  instance.isUnmounted = true
}

3.6 编译阶段

  流程:

  1. 将模板解析为AST
  2. 转换AST静态提升/标记PatchFlag
  3. 生成可执行的渲染函数代码
  4. 运行时直接使用优化后的渲染函数

  关键代码:

// compiler-core/src/compile.ts
function baseCompile(template, options) {
  // 1. 解析阶段
  const ast = parse(template)
  
  // 2. 转换阶段
  transform(ast, {
    hoistStatic: true,  // 静态提升
    nodeTransforms: [
      transformIf,
      transformFor,
      transformExpression
    ]
  })
  
  // 3. 代码生成
  const code = generate(ast, {
    mode: 'function',
    runtimeGlobalName: 'Vue',
    prefixIdentifiers: true
  })
  
  return {
    ast,
    code: `with(this){return ${code}}`  // 生成渲染函数
  }
}