Skip to content

Vue 3 源码深度解析

1. Vue 3 架构概述

1.1 核心模块划分

Vue 3 采用了模块化的架构设计,将核心功能拆分为多个独立的包,每个包都有明确的职责:

  • @vue/runtime-core: 核心运行时,提供组件系统、虚拟DOM、响应式等基础功能
  • @vue/runtime-dom: DOM平台特定实现,处理浏览器DOM操作
  • @vue/compiler-core: 核心编译器,将模板转换为渲染函数
  • @vue/compiler-dom: 针对浏览器平台的编译器扩展
  • @vue/reactivity: 响应式系统,可独立使用
  • @vue/compiler-sfc: 单文件组件(.vue)编译器
  • @vue/server-renderer: 服务端渲染支持

1.2 源码目录结构

packages/
├── compiler-core/       # 核心编译器
├── compiler-dom/        # 浏览器平台编译器
├── compiler-sfc/        # 单文件组件编译器
├── compiler-ssr/        # 服务端渲染编译器
├── reactivity/          # 响应式系统
├── runtime-core/        # 核心运行时
├── runtime-dom/         # DOM运行时
├── runtime-test/        # 测试运行时
├── server-renderer/     # 服务端渲染
├── shared/              # 共享工具函数
├── size-check/          # 大小检查
├── template-explorer/   # 模板探索器
└── vue/                 # 完整构建,整合所有模块

1.3 MVVM 架构详解

MVVM (Model-View-ViewModel) 是Vue框架实现的核心架构模式,它提供了数据与视图的自动同步机制,使开发者能够专注于业务逻辑而不必手动操作DOM。

1.3.1 MVVM 架构组成

Model(模型)

  • 定义:应用的数据层,包含业务逻辑和数据结构
  • 职责:存储和管理应用数据,处理业务规则
  • 在Vue中:对应data选项、reactiveref等响应式数据
javascript
// Model 示例 - 响应式数据
const user = reactive({
  name: 'Vue Developer',
  age: 30
})

// 业务逻辑函数
function updateUserInfo(newName, newAge) {
  user.name = newName
  user.age = newAge
}

View(视图)

  • 定义:用户界面层,负责数据的可视化展示
  • 职责:将ViewModel的数据转换为UI元素,捕获用户交互
  • 在Vue中:对应组件的模板(template)部分
html
<!-- View 示例 - 模板 -->
<template>
  <div class="user-info">
    <h1>{{ user.name }}</h1>
    <p>年龄: {{ user.age }}</p>
    <button @click="updateInfo">更新信息</button>
  </div>
</template>

ViewModel(视图模型)

  • 定义:连接Model和View的桥梁
  • 职责:监听Model变化并更新View,处理View事件并更新Model
  • 在Vue中:对应Vue实例/组件实例,包含methodscomputedwatch
javascript
// ViewModel 示例 - 组件实例
const vm = {
  setup() {
    // 连接Model和View
    const user = reactive({ name: 'Vue Developer', age: 30 })
    
    // 处理用户交互
    const updateInfo = () => {
      user.name = 'Updated Name'
      user.age = 31
    }
    
    // 提供给视图的数据和方法
    return {
      user,
      updateInfo
    }
  }
}

1.3.2 MVVM 数据绑定机制

Vue通过数据劫持发布-订阅模式实现了MVVM的双向数据绑定:

  1. 数据劫持:使用Proxy(Vue 3)或Object.defineProperty(Vue 2)拦截数据访问和修改
  2. 依赖收集:在数据被访问时收集依赖(哪些组件使用了这个数据)
  3. 通知更新:在数据被修改时通知所有依赖的组件更新视图

数据绑定流程源码解析

javascript
// 数据劫持核心代码
function reactive(target) {
  // 创建Proxy代理
  return new Proxy(target, {
    get(target, key, receiver) {
      // 获取原始值
      const result = Reflect.get(target, key, receiver)
      // 依赖收集
      track(target, TrackOpTypes.GET, key)
      // 递归处理嵌套对象
      if (isObject(result)) {
        return reactive(result)
      }
      return result
    },
    set(target, key, value, receiver) {
      // 获取旧值
      const oldValue = target[key]
      // 设置新值
      const result = Reflect.set(target, key, value, receiver)
      // 如果值发生变化,触发更新
      if (hasChanged(value, oldValue)) {
        // 通知依赖更新
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
      return result
    }
    // 其他拦截器...
  })
}

1.3.3 MVVM 工作原理示例

下面通过一个完整的Vue组件示例,展示MVVM架构的工作流程:

javascript
// 组件定义(ViewModel)
export default {
  name: 'UserProfile',
  // Model - 数据层
  setup() {
    // 响应式数据(Model)
    const user = reactive({
      name: 'John Doe',
      email: 'john@example.com',
      role: 'admin'
    })
    
    // 业务逻辑(Model)
    const updateUserRole = (newRole) => {
      if (['admin', 'user', 'guest'].includes(newRole)) {
        user.role = newRole
      }
    }
    
    // 计算属性(ViewModel)
    const displayRole = computed(() => {
      const roleMap = {
        admin: '管理员',
        user: '普通用户',
        guest: '访客'
      }
      return roleMap[user.role] || '未知角色'
    })
    
    // 事件处理(ViewModel)
    const handleSubmit = () => {
      console.log('提交用户数据:', user)
      // 模拟API调用
    }
    
    // 暴露给视图的数据和方法
    return {
      user,
      displayRole,
      updateUserRole,
      handleSubmit
    }
  }
}
html
<!-- 组件模板(View) -->
<template>
  <div class="user-profile">
    <h2>用户资料</h2>
    
    <!-- 数据绑定:Model -> View -->
    <div class="form-group">
      <label>用户名:</label>
      <input v-model="user.name" placeholder="输入用户名">
    </div>
    
    <div class="form-group">
      <label>邮箱:</label>
      <input v-model="user.email" type="email" placeholder="输入邮箱">
    </div>
    
    <div class="form-group">
      <label>角色:</label>
      <select v-model="user.role" @change="updateUserRole(user.role)">
        <option value="admin">管理员</option>
        <option value="user">普通用户</option>
        <option value="guest">访客</option>
      </select>
    </div>
    
    <!-- 计算属性展示:ViewModel -> View -->
    <p>当前角色: {{ displayRole }}</p>
    
    <!-- 事件绑定:View -> ViewModel -> Model -->
    <button @click="handleSubmit">保存资料</button>
  </div>
</template>

1.3.4 MVVM 架构的优势

  1. 关注点分离:数据处理和UI展示分离,提高代码可维护性
  2. 自动数据同步:数据变化自动更新视图,视图交互自动更新数据
  3. 声明式编程:开发者只需声明数据与视图的关系,不必编写命令式代码
  4. 可测试性:ViewModel可以独立于View进行单元测试
  5. 组件复用:相同的业务逻辑可以在不同视图中复用

1.3.5 面试题:MVVM vs MVC vs MVP

MVVM vs MVC

特性MVVMMVC
核心差异双向数据绑定单向数据流
控制层ViewModel(自动同步)Controller(手动更新)
数据流向Model ↔ ViewModel ↔ ViewModel → View, User Action → Controller → Model
适用场景单页应用、交互式UI传统Web应用、页面切换频繁的场景

MVVM vs MVP

特性MVVMMVP
视图更新自动(数据绑定)手动(Presenter调用View接口)
依赖关系View对ViewModel依赖弱View对Presenter依赖强
代码量更少(减少模板代码)更多(需要手动同步)
复杂性更高(自动绑定机制复杂)更低(逻辑清晰)

2. Vue 3 初始化过程

2.1 创建应用实例

javascript
// createApp API 定义
function createApp(rootComponent, rootProps = null) {
  if (!isFunction(rootComponent)) {
    rootComponent = { ...rootComponent }
  }

  const context = createAppContext()
  const app = (context.app = {
    _uid: uid++,
    _component: rootComponent,
    _props: rootProps,
    _container: null,
    _context: context,
    _instance: null,
    
    use(plugin, ...options) {
      // 插件安装逻辑
      if (plugin && isFunction(plugin.install)) {
        plugin.install(app, ...options)
      } else if (isFunction(plugin)) {
        plugin(app, ...options)
      }
      return app
    },
    
    mixin(mixin) {
      // 混入逻辑
      if (!context.mixins.includes(mixin)) {
        context.mixins.push(mixin)
      }
      return app
    },
    
    component(name, component) {
      // 组件注册逻辑
      if (!component) {
        return context.components[name]
      }
      context.components[name] = component
      return app
    },
    
    directive(name, directive) {
      // 指令注册逻辑
      if (!directive) {
        return context.directives[name]
      }
      context.directives[name] = directive
      return app
    },
    
    mount(rootContainer, isHydrate = false, isSVG = false) {
      // 挂载应用逻辑
      // ...
    },
    
    unmount() {
      // 卸载应用逻辑
      // ...
    }
  })
  
  return app
}

2.2 应用挂载流程

javascript
// 挂载应用
app.mount(rootContainer, isHydrate = false, isSVG = false) {
  if (!isFunction(this._component)) {
    this._component = { ...this._component }
  }
  
  // 解析容器
  const container = normalizeContainer(rootContainer)
  if (!container) return
  
  // 获取原始容器内容
  const component = this._component
  
  // 创建vnode
  const vnode = createVNode(component, this._props)
  
  // 存储应用实例引用
  vnode.appContext = this._context
  
  // 渲染vnode到容器
  if (isHydrate && hydrate) {
    hydrate(vnode, container)
  } else {
    render(vnode, container, isSVG)
  }
  
  // 标记已挂载
  this._container = container
  
  // 返回组件实例
  const proxy = container.__vue_app__ = this
  
  return getExposeProxy(vnode.component) || vnode.component.proxy
}

3. 响应式系统源码分析

3.1 响应式核心API

3.1.1 监听API对比分析

Vue 3提供了多种监听数据变化的API,每种API都有其特定的使用场景和执行时机。下面详细对比这些API的区别和实践应用:

watch API

javascript
function watch(
  source: WatchSource | WatchSource[] | object,
  callback: WatchCallback,
  options?: WatchOptions
): WatchStopHandle

interface WatchOptions {
  immediate?: boolean // 是否立即执行
  deep?: boolean // 是否深度监听
  flush?: 'pre' | 'post' | 'sync' // 执行时机
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  once?: boolean // 是否只监听一次
}

特点:

  • 可以明确指定监听的数据源
  • 可以获取变化前后的值
  • 支持惰性执行(默认不立即执行)
  • 可配置深度监听和执行时机

使用场景:

  • 需要明确知道数据变化前后的值
  • 需要对特定数据属性进行监听
  • 需要控制监听的执行时机

示例:

javascript
// 监听单个响应式数据
watch(
  () => user.name,
  (newName, oldName) => {
    console.log(`用户名从 ${oldName} 变为 ${newName}`)
  },
  { immediate: true, deep: false }
)

// 监听多个数据源
watch(
  [() => user.name, () => user.age],
  ([newName, newAge], [oldName, oldAge]) => {
    console.log('用户信息更新:', { newName, newAge })
  }
)

watchEffect API

javascript
function watchEffect(
  effect: (onCleanup: OnCleanupFn) => void,
  options?: WatchEffectOptions
): WatchStopHandle

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 执行时机
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  onStop?: () => void
}

特点:

  • 自动收集依赖,不需要明确指定数据源
  • 立即执行一次,然后在依赖变化时重新执行
  • 无法直接获取变化前后的值
  • 返回一个停止函数

使用场景:

  • 副作用操作依赖多个响应式数据
  • 需要在数据变化时自动执行某些操作
  • 不需要关心变化前后的具体值

示例:

javascript
const stopWatch = watchEffect((onCleanup) => {
  // 自动收集user.name和user.age作为依赖
  const data = fetchUserData(user.name, user.age)
  
  // 清理函数,在重新执行前或组件卸载时调用
  onCleanup(() => {
    data.abort() // 取消请求
  })
})

// 需要时停止监听
// stopWatch()

watchPostEffect API

javascript
function watchPostEffect(
  effect: (onCleanup: OnCleanupFn) => void,
  options?: DebuggerOptions
): WatchStopHandle

特点:

  • watchEffect的别名,等同于watchEffect(effect, { flush: 'post' })
  • 在DOM更新后执行副作用
  • 适用于需要在DOM更新后访问DOM的场景

使用场景:

  • 需要在DOM更新后执行的副作用
  • 访问更新后的DOM状态
  • 避免在DOM更新前进行DOM操作导致的问题

示例:

javascript
watchPostEffect(() => {
  // DOM已更新,可以安全地访问更新后的DOM
  const element = document.getElementById('user-profile')
  if (element) {
    element.scrollTop = element.scrollHeight // 滚动到底部
  }
})

watchSyncEffect API

javascript
function watchSyncEffect(
  effect: (onCleanup: OnCleanupFn) => void,
  options?: DebuggerOptions
): WatchStopHandle

特点:

  • watchEffect的别名,等同于watchEffect(effect, { flush: 'sync' })
  • 同步执行副作用,响应式数据变化时立即触发
  • 可能导致性能问题,应谨慎使用

使用场景:

  • 需要立即响应数据变化
  • 对实时性要求极高的场景
  • 不关心性能影响的场景

示例:

javascript
watchSyncEffect(() => {
  // 数据变化时立即执行,不等待批处理
  console.log('立即执行:', user.value)
  // 注意:频繁调用可能导致性能问题
})

3.1.2 监听API执行时机对比

API默认执行时机执行特点适用场景
watch'pre' (组件更新前)可配置,默认惰性执行需要精确控制执行时机和获取变化前后的值
watchEffect'pre' (组件更新前)立即执行,自动收集依赖副作用依赖多个响应式数据
watchPostEffect'post' (组件更新后)DOM更新后执行需要访问更新后的DOM
watchSyncEffect'sync' (同步)数据变化立即执行对实时性要求极高的场景

3.1.3 源码实现对比

watch 实现核心

javascript
function doWatch(
  source: WatchSource | WatchSource[] | object | null,
  cb: WatchCallback | null,
  { immediate, deep, flush, once }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  // ...
  
  // 创建effect
  const effect = new ReactiveEffect(
    getter,
    // scheduler决定执行时机
    () => {
      if (effect.dirty) {
        if (flush === 'sync') {
          // 同步执行
          job()
        } else if (flush === 'post') {
          // 异步后置执行
          queuePostRenderEffect(job, instance && instance.suspense)
        } else {
          // 默认前置执行
          queuePreFlushCb(job)
        }
      }
    }
  )
  
  // ...
}

watchEffect 实现核心

javascript
function watchEffect(
  effect: WatchEffect,
  options?: WatchEffectOptions
): WatchStopHandle {
  return doWatch(effect, null, options)
}

// watchPostEffect 是 watchEffect 的别名
const watchPostEffect: typeof watchEffect = (effect, options) => {
  return watchEffect(effect, {
    ...options,
    flush: 'post'
  })
}

// watchSyncEffect 是 watchEffect 的别名
const watchSyncEffect: typeof watchEffect = (effect, options) => {
  return watchEffect(effect, {
    ...options,
    flush: 'sync'
  })
}

3.1.4 实践操作建议

  1. 选择合适的API

    • 需要访问变化前后的值:使用 watch
    • 副作用依赖多个响应式数据:使用 watchEffect
    • 需要在DOM更新后操作:使用 watchPostEffect
    • 实时性要求极高:使用 watchSyncEffect(谨慎使用)
  2. 性能优化技巧

    • 避免使用 deep: true,尽可能精确监听需要的属性
    • 使用 watchPostEffect 代替 watchnextTick
    • 及时清理不需要的监听:调用返回的停止函数
    • 避免在监听回调中进行复杂计算或阻塞操作
  3. 常见使用场景

    • 数据持久化:监听表单数据变化并自动保存
    javascript
    watch(
      () => formData,
      (newData) => {
        localStorage.setItem('formData', JSON.stringify(newData))
      },
      { deep: true }
    )
    • API调用优化:避免频繁请求
    javascript
    let timeoutId
    watch(
      () => searchQuery,
      (newQuery) => {
        clearTimeout(timeoutId)
        timeoutId = setTimeout(() => {
          fetchSearchResults(newQuery)
        }, 300) // 防抖
      }
    )
    • DOM操作:在DOM更新后执行
    javascript
    watchPostEffect(() => {
      // 安全地操作DOM
      highlightActiveElement()
    })

3.1.5 面试题:监听API的区别

Q1: watch 和 watchEffect 的主要区别是什么?

  • 依赖收集:watch需要明确指定监听源,watchEffect自动收集依赖
  • 执行时机:watch默认惰性执行,watchEffect立即执行
  • 参数获取:watch可以获取变化前后的值,watchEffect不能
  • 使用场景:watch适用于需要精确控制的场景,watchEffect适用于副作用依赖多个数据的场景

Q2: flush 选项的不同值有什么影响?

  • 'pre':组件更新前执行,默认值
  • 'post':组件更新后执行,DOM已更新
  • 'sync':数据变化时立即同步执行

Q3: 如何优化深层监听的性能?

  • 使用函数返回需要监听的具体属性,而不是整个对象
  • 避免使用 deep: true,改为监听特定嵌套属性
  • 使用computed先派生数据,再监听派生结果

3.1.6 响应式核心API

reactive 函数

javascript
function reactive(target) {
  // 如果目标已经是一个响应式代理,直接返回
  if (target && target.__v_isReactive) {
    return target
  }
  // 创建响应式代理
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

function createReactiveObject(
  target,
  isReadonly,
  baseHandlers,
  collectionHandlers,
  proxyMap
) {
  // 只有对象类型可以被响应式化
  if (!isObject(target)) {
    return target
  }
  
  // 检查是否已经存在代理
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  // 为集合类型使用不同的处理器
  const handlers = collectionTypes.has(target.constructor) 
    ? collectionHandlers 
    : baseHandlers
  
  // 创建代理
  const proxy = new Proxy(target, handlers)
  
  // 缓存代理
  proxyMap.set(target, proxy)
  return proxy
}

ref 函数

javascript
function ref(value) {
  return createRef(value)
}

function createRef(rawValue, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  
  return new RefImpl(rawValue, shallow)
}

class RefImpl {
  private _value
  private _rawValue
  
  public dep
  public readonly __v_isRef = true
  
  constructor(value, public readonly __v_isShallow = false) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : convert(value)
  }
  
  get value() {
    trackRefValue(this)
    return this._value
  }
  
  set value(newVal) {
    const useDirectValue = this.__v_isShallow || isRef(newVal)
    const newValUnwrapped = useDirectValue ? newVal : toRaw(newVal)
    if (newValUnwrapped !== this._rawValue) {
      this._rawValue = newValUnwrapped
      this._value = useDirectValue ? newVal : convert(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

computed 函数

javascript
function computed(getterOrOptions) {
  let getter
  let setter
  
  // 处理传入参数
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = () => {
      console.warn('Write operation failed: computed value is readonly')
    }
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  
  // 创建计算属性ref
  return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions))
}

class ComputedRefImpl {
  private _value
  private _dirty = true
  
  public dep
  public readonly effect
  
  public readonly __v_isRef = true
  public readonly [ReactiveFlags.IS_READONLY]
  
  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this)
      }
    })
    this.effect.computed = this
    this.effect.active = true
    this[ReactiveFlags.IS_READONLY] = isReadonly
  }
  
  get value() {
    // 追踪依赖
    trackRefValue(this)
    // 只有在脏时才重新计算
    if (this._dirty) {
      this._dirty = false
      this._value = this.effect.run()
    }
    return this._value
  }
  
  set value(newValue) {
    this._setter(newValue)
  }
}

3.2 依赖追踪与触发

track 函数

javascript
function track(target, type, key) {
  // 只有在追踪期间才进行依赖收集
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  
  // 获取目标对象的依赖映射
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  
  // 获取属性的依赖集合
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = createDep()))
  }
  
  // 收集当前活跃的effect
  trackEffects(dep)
}

function trackEffects(dep) {
  const shouldTrack = !dep.has(activeEffect)
  if (shouldTrack) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
  }
}

trigger 函数

javascript
function trigger(target, type, key, newValue, oldValue, oldTarget) {
  // 获取依赖映射
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }
  
  // 收集需要触发的effects
  const effects = new Set()
  const add = (effectsToAdd) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect) {
          effects.add(effect)
        }
      })
    }
  }
  
  // 针对不同类型的操作进行处理
  if (type === TriggerType.CLEAR) {
    // 清除操作需要触发所有键的依赖
    depsMap.forEach(dep => add(dep))
  } else if (key === 'length' && isArray(target)) {
    // 数组长度变化特殊处理
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // 处理特定键的依赖
    if (key !== void 0) {
      add(depsMap.get(key))
    }
    
    // 处理ADD/DELETE操作的特殊情况
    if (type === TriggerType.ADD && isArray(target)) {
      add(depsMap.get('length'))
    }
  }
  
  // 触发effects
  const run = (effect) => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect.run()
    }
  }
  
  effects.forEach(run)
}

4. 虚拟DOM 与渲染系统

4.1 虚拟DOM 创建

VNode 结构

javascript
interface VNode {
  __v_isVNode: true
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT
  props: (VNodeProps & ExtraProps) | null
  key: string | number | null
  ref: VNodeRef | null
  scopeId: string | null
  children: VNodeNormalizedChildren
  component: ComponentInternalInstance | null
  dirs: DirectiveBinding[] | null
  transition: TransitionHooks<HostElement> | null
  el: HostNode | null
  anchor: HostNode | null
  target: HostElement | null
  targetAnchor: HostNode | null
  staticCount: number
  shapeFlag: number
  patchFlag: number
  dynamicProps: string[] | null
  dynamicChildren: VNode[] | null
  appContext: AppContext | null
}

4.1.1 VNode 参数工作原理解析

Vue 3的虚拟DOM (VNode) 是渲染系统的核心数据结构,每个参数都有其特定的用途和工作原理。下面详细分析每个参数的作用和工作机制:

1. 基础标识参数

__v_isVNode

  • 类型: boolean
  • 工作原理: 用于标识一个对象是否为VNode实例,避免与普通对象混淆
  • 使用场景: 在渲染过程中快速判断对象类型,优化性能

type

  • 类型: string | Component | Fragment | Text | Comment | ...
  • 工作原理: 决定VNode的类型,渲染器根据type采取不同的处理策略
  • 使用场景:
    • string: 普通HTML元素 (如 'div', 'span')
    • Component: 组件定义
    • Symbol(Fragment): 片段节点,用于渲染多根元素
    • Symbol(Text): 纯文本节点
    • Symbol(Comment): 注释节点

props

  • 类型: Object | null
  • 工作原理: 存储节点的属性、事件监听器、指令等
  • 使用场景: 在patch阶段用于更新DOM元素的属性和事件

2. 优化相关参数

key

  • 类型: string | number | null
  • 工作原理: 在列表渲染时用于识别节点的唯一性,决定如何高效地更新DOM
  • 使用场景:
    • 避免不必要的DOM重新创建
    • 保持组件状态和位置关系
    • 优化列表更新性能
  • 示例对比:
javascript
// 无key的情况 - 可能导致状态错位
<div v-for="item in items">{{ item.text }}</div>

// 有key的情况 - 正确识别并更新节点
<div v-for="item in items" :key="item.id">{{ item.text }}</div>

shapeFlag

  • 类型: number (位掩码)
  • 工作原理: 使用位运算高效标识VNode的类型和子节点类型
  • 主要标识:
    • ShapeFlags.ELEMENT: 普通元素
    • ShapeFlags.COMPONENT: 组件
    • ShapeFlags.TEXT_CHILDREN: 文本子节点
    • ShapeFlags.ARRAY_CHILDREN: 数组子节点
  • 使用场景: 渲染器快速判断VNode类型,决定后续处理逻辑

patchFlag

  • 类型: number (位掩码)
  • 工作原理: Vue 3的核心优化,标识VNode中哪些部分是动态变化的
  • 主要标识:
    • PatchFlags.TEXT: 只有文本内容是动态的
    • PatchFlags.CLASS: 只有class是动态的
    • PatchFlags.STYLE: 只有style是动态的
    • PatchFlags.PROPS: 特定props是动态的
    • PatchFlags.FULL_PROPS: 所有props都可能是动态的
    • PatchFlags.HYDRATE_EVENTS: 需要在服务端渲染水合时绑定事件
  • 使用场景: 渲染器跳过静态部分,只更新变化的动态内容,大幅提升性能

dynamicProps

  • 类型: string[] | null
  • 工作原理: 与patchFlag配合使用,精确指定哪些props是动态变化的
  • 使用场景: 当patchFlag为PROPS时,明确需要比对的props列表,避免全量props比对

dynamicChildren

  • 类型: VNode[] | null
  • 工作原理: Block Tree优化,存储需要动态更新的子节点
  • 使用场景: 跳过静态子树,直接定位并更新动态子节点,减少Diff算法的遍历范围

3. 引用和关联参数

ref

  • 类型: VNodeRef | null
  • 工作原理: 用于获取真实DOM元素或组件实例的引用
  • 使用场景:
    • 通过模板refs访问DOM元素
    • 在组合式API中通过ref()函数获取组件实例
  • 示例:
javascript
// 模板中使用
<div ref="myElement"></div>

// 组合式API中使用
const myElement = ref(null)

el

  • 类型: HostNode | null
  • 工作原理: 指向VNode对应的真实DOM节点
  • 使用场景: 在渲染过程中建立虚拟DOM和真实DOM的映射关系

component

  • 类型: ComponentInternalInstance | null
  • 工作原理: 存储组件实例信息
  • 使用场景: 组件相关操作,如访问组件状态、生命周期等

4. 特殊节点参数

anchor

  • 类型: HostNode | null
  • 工作原理: 用于Fragment等特殊节点,作为插入位置的参考点
  • 使用场景: 多根元素渲染时确定插入位置

target / targetAnchor

  • 类型: HostElement | null / HostNode | null
  • 工作原理: 用于Teleport组件,指定内容要移动到的目标位置
  • 使用场景: Teleport组件将内容渲染到DOM树的其他位置

transition

  • 类型: TransitionHooks<HostElement> | null
  • 工作原理: 存储过渡效果相关的钩子函数
  • 使用场景: 用于v-show和v-if等指令的过渡动画效果

5. 其他重要参数

children

  • 类型: VNodeNormalizedChildren
  • 工作原理: 存储子节点,可以是文本、数组或对象
  • 使用场景: 构建DOM树的层次结构

scopeId

  • 类型: string | null
  • 工作原理: 用于CSS作用域,实现样式隔离
  • 使用场景: 在单文件组件中,为元素添加data-v-*属性

dirs

  • 类型: DirectiveBinding[] | null
  • 工作原理: 存储应用在节点上的指令
  • 使用场景: 处理自定义指令的逻辑

appContext

  • 类型: AppContext | null
  • 工作原理: 存储应用上下文信息,包含全局属性、插件等
  • 使用场景: 在组件树中传递全局配置

4.1.2 VNode 参数使用对比示例

下面通过几个具体示例对比不同参数的使用效果和性能影响:

示例1: key 参数的作用

javascript
// 有key的情况
const withKey = createApp({
  setup() {
    const items = ref([{ id: 1, text: 'A' }, { id: 2, text: 'B' }, { id: 3, text: 'C' }])
    
    const reverse = () => {
      items.value = items.value.reverse()
    }
    
    return { items, reverse }
  },
  template: `
    <button @click="reverse">反转</button>
    <div v-for="item in items" :key="item.id">
      <input v-model="item.text" />
    </div>
  `
})

// 无key的情况 (性能较差)
const withoutKey = createApp({
  setup() {
    // 同上
  },
  template: `
    <button @click="reverse">反转</button>
    <div v-for="item in items">
      <input v-model="item.text" />
    </div>
  `
})

性能对比:

  • 有key: 只重新排序DOM节点,保持输入框的状态
  • 无key: 重新创建DOM节点,输入框状态丢失
  • 性能差异: 有key的版本在列表更新时DOM操作减少约60-80%

示例2: patchFlag 的优化效果

javascript
// 使用不同patchFlag的VNode创建
function createOptimizedVNode() {
  // 只有文本内容变化 - 最高效
  return createVNode('div', null, dynamicText, ShapeFlags.ELEMENT, PatchFlags.TEXT)
}

function createNormalVNode() {
  // 未指定patchFlag - 低效
  return createVNode('div', null, dynamicText)
}

// 在patch阶段的性能影响
function patchWithFlag(n1, n2) {
  // 仅更新文本内容,跳过props等检查
  if (n2.patchFlag & PatchFlags.TEXT) {
    if (n2.children !== n1.children) {
      hostSetElementText(n2.el, n2.children)
    }
  }
}

function patchWithoutFlag(n1, n2) {
  // 需要全面比对所有属性和子节点
  // 检查props
  if (hasPropsChanged(n1, n2)) {
    updateProps(n1, n2)
  }
  // 检查子节点
  if (hasChildrenChanged(n1, n2)) {
    patchChildren(n1, n2)
  }
}

性能对比:

  • 有patchFlag: 直接定位到变化部分,避免全量比对
  • 无patchFlag: 需要递归比对所有属性和子节点
  • 性能差异: 在复杂组件树中,优化后可提升50-70%的更新性能

示例3: dynamicChildren 与 Block Tree

javascript
// 渲染函数中使用Block Tree
function renderWithBlocks() {
  return (
    // createBlock创建根Block节点
    _createBlock('div', null, [
      '静态文本',
      // 动态文本节点会被标记
      _createVNode('span', null, dynamicText, 0, PatchFlags.TEXT),
      '更多静态内容',
      // 动态组件
      _createVNode(MyComponent, { value: dynamicValue })
    ])
  )
}

// 优化后的patch过程
function optimizedPatch() {
  // 直接访问dynamicChildren,跳过静态节点
  if (n2.dynamicChildren) {
    for (let i = 0; i < n2.dynamicChildren.length; i++) {
      const oldChild = n1.dynamicChildren[i]
      const newChild = n2.dynamicChildren[i]
      patch(oldChild, newChild, container)
    }
  }
}

性能对比:

  • 启用Block Tree: 只处理动态子节点,跳过大量静态内容
  • 禁用Block Tree: 递归处理所有子节点
  • 性能差异: 在静态内容多的组件中,性能提升可达300-500%

4.1.3 虚拟DOM优化原理总结

Vue 3通过以下核心机制显著提升了虚拟DOM的性能:

  1. 静态提升: 编译时识别静态节点并提升到渲染函数外部,避免重复创建

  2. PatchFlags: 精确标记动态内容,跳过静态部分

  3. Block Tree: 将动态节点收集到特定数组,直接跳过静态树的比对

  4. 缓存优化: 对相同类型和属性的VNode进行缓存复用

  5. 位运算优化: 使用位掩码(shapeFlag, patchFlag)高效表示和判断节点状态

这些优化使得Vue 3的虚拟DOM在大多数场景下性能比Vue 2提升2-3倍,特别是在大型静态内容和列表渲染场景中表现尤为突出。

createVNode 函数

javascript
function createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null
): VNode {
  // 规范化props
  if (props) {
    // 处理props相关逻辑
  }
  
  // 优化:针对同一类型和props的静态VNode进行缓存
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
      ? ShapeFlags.SUSPENSE
      : isTeleport(type)
        ? ShapeFlags.TELEPORT
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT
            : 0
  
  // 创建VNode实例
  return createBaseVNode(
    type,
    props,
    children,
    shapeFlag,
    undefined,
    undefined,
    true
  )
}

function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  shapeFlag = ShapeFlags.ELEMENT,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  const vnode = {
    __v_isVNode: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    children: null,
    component: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  }
  
  // 处理子节点
  normalizeChildren(vnode, children)
  
  // 设置patchFlag和block相关标记
  if (__DEV__) {
    // 开发环境检查
  }
  
  return vnode
}

4.2 渲染器核心

render 函数

javascript
function render(vnode: VNode | null, container: HostElement, isSVG: boolean = false) {
  const prevVNode = container._vnode
  const unmountChildren = prevVNode
    ? prevVNode.type === Fragment
      ? prevVNode.children
      : [prevVNode]
    : null
  
  // 清除旧内容
  if (prevVNode) {
    unmount(prevVNode, parentComponent, parentSuspense, true)
  }
  
  // 渲染新内容
  if (vnode) {
    patch(null, vnode, container, null, null, null, isSVG)
  }
  
  // 卸载旧子节点
  if (unmountChildren) {
    unmountChildren.forEach(c => unmount(c))
  }
  
  // 存储当前vnode
  container._vnode = vnode
}

patch 函数

javascript
function patch(
  n1: VNode | null,
  n2: VNode,
  container: HostElement,
  anchor: HostNode | null = null,
  parentComponent: ComponentInternalInstance | null = null,
  parentSuspense: SuspenseBoundary | null = null,
  isSVG: boolean = false,
  slotScopeIds: string[] | null = null,
  optimized: boolean = false
) {
  // 如果新旧节点相同,跳过
  if (n1 === n2) {
    return
  }
  
  // 处理n2为null的情况
  if (n2.patchFlag === PatchFlags.BAIL) {
    optimized = false
    n2.dynamicChildren = null
  }
  
  // 记录新节点的容器
  const { type, ref, shapeFlag } = n2
  
  // 根据节点类型进行不同的处理
  if (isString(type)) {
    // 处理元素节点
    processElement(
      n1,
      n2,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else if (isTeleport(type)) {
    // 处理Teleport组件
    processTeleport(
      n1,
      n2,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else if (isSuspense(type)) {
    // 处理Suspense组件
    processSuspense(
      n1,
      n2,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else if (shapeFlag & ShapeFlags.COMPONENT) {
    // 处理组件节点
    processComponent(
      n1,
      n2,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else if (shapeFlag & ShapeFlags.FRAGMENT) {
    // 处理Fragment
    processFragment(
      n1,
      n2,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
    // 处理纯文本节点
    if (n1) {
      if (n2.children !== n1.children) {
        hostSetElementText(container, n2.children as string)
      }
    } else {
      hostSetElementText(container, n2.children as string)
    }
  }
  
  // 处理ref
  if (ref != null && parentComponent) {
    setRef(ref, n1 && n1.ref, parentComponent, n2)
  }
}

5. 组件系统实现

5.1 组件创建与挂载

mountComponent 函数

javascript
function mountComponent(
  initialVNode: VNode,
  container: HostElement,
  anchor: HostNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  optimized: boolean
) {
  // 创建组件实例
  const instance: ComponentInternalInstance = (
    initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    )
  )
  
  // 设置组件实例
  setupComponent(instance)
  
  // 设置渲染效果
  setupRenderEffect(
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  )
}

function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  const type = vnode.type as ConcreteComponent
  
  const instance: ComponentInternalInstance = {
    uid: uid++,
    vnode,
    type,
    parent,
    appContext: parent ? parent.appContext : vnode.appContext,
    root: parent ? parent.root : null,
    next: null,
    subTree: null,
    update: null,
    render: null,
    proxy: null,
    withProxy: null,
    effects: null,
    provides: parent ? parent.provides : Object.create(appContext.provides),
    injects: null,
    propsOptions: normalizePropsOptions(type, appContext),
    emitsOptions: normalizeEmitsOptions(type, appContext),
    propsDefaults: undefined,
    accessCache: null,
    renderCache: [],
    ctx: EMPTY_OBJ,
    data: EMPTY_OBJ,
    props: EMPTY_PROPS,
    attrs: EMPTY_OBJ,
    slots: EMPTY_OBJ,
    refs: EMPTY_OBJ,
    setupState: EMPTY_OBJ,
    suspense,
    asyncDep: null,
    asyncResult: null,
    isMounted: false,
    isUnmounted: false,
    isDeactivated: false,
    [ReactiveFlags.SKIP]: true
  }
  
  return instance
}

setupComponent 函数

javascript
function setupComponent(instance: ComponentInternalInstance) {
  const { props, children } = instance.vnode
  
  // 处理props
  const setupResult = setupStatefulComponent(instance)
  
  return setupResult
}

function setupStatefulComponent(instance: ComponentInternalInstance) {
  const Component = instance.type as ComponentOptions
  
  // 创建渲染上下文代理
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
  
  // 调用setup函数
  const { setup } = Component
  if (setup) {
    // 准备setup上下文
    const setupContext = (instance.setupContext = setup.length > 1
      ? createSetupContext(instance)
      : null)
    
    // 执行setup函数
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    
    // 处理setup返回值
    handleSetupResult(instance, setupResult)
  } else {
    // 没有setup函数时,直接完成组件设置
    finishComponentSetup(instance)
  }
}

5.2 组件更新流程

setupRenderEffect 函数

javascript
function setupRenderEffect(
  instance: ComponentInternalInstance,
  initialVNode: VNode,
  container: HostElement,
  anchor: HostNode | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  optimized: boolean
) {
  const componentUpdateFn = () => {
    const { proxy, vnode, next, bu, u, parent, vnodeEl } = instance
    
    // 更新组件实例
    if (!instance.isMounted) {
      // 首次挂载
      // 调用beforeMount钩子
      if (bu) {
        callSyncHook(bu, instance)
      }
      
      // 渲染组件
      const subTree = (instance.subTree = renderComponentRoot(instance))
      
      // 挂载子树
      patch(null, subTree, container, anchor, parent, parentSuspense, isSVG)
      
      // 标记为已挂载
      instance.isMounted = true
      
      // 调用mounted钩子
      if (m) {
        queuePostFlushCb(() => {
          callSyncHook(m, instance)
        })
      }
    } else {
      // 更新阶段
      // 调用beforeUpdate钩子
      if (u) {
        callSyncHook(u, instance)
      }
      
      // 获取最新的vnode
      const nextTree = renderComponentRoot(instance)
      const prevTree = instance.subTree
      instance.subTree = nextTree
      
      // 对比新旧子树
      patch(
        prevTree,
        nextTree,
        prevTree.el!.parentNode,
        null,
        parent,
        parentSuspense,
        isSVG
      )
      
      // 调用updated钩子
      if (um) {
        queuePostFlushCb(() => {
          callSyncHook(um, instance)
        })
      }
    }
  }
  
  // 创建响应式effect
  const effect = (instance.effect = new ReactiveEffect(
    componentUpdateFn,
    () => queueJob(update),
    instance.scope
  ))
  
  const update = (instance.update = () => effect.run())
  
  // 执行首次更新
  update()
}

6. 编译器优化

6.1 编译流程

javascript
function compile(template: string, options: CompilerOptions = {}): CodegenResult {
  // 解析模板生成AST
  const ast = parse(template.trim(), options)
  
  // 优化AST(标记静态节点等)
  transform(ast, {
    ...options,
    nodeTransforms: [...nodeTransforms, ...(options.nodeTransforms || [])]
  })
  
  // 生成渲染函数代码
  return generate(ast, {
    ...options,
    transformHoist: null
  })
}

6.2 静态提升

javascript
function hoistStatic(node, context) {
  // 标记可以静态提升的节点
  if (isStatic(node)) {
    node.codegenNode = context.hoist(node.codegenNode)
  } else if (node.type === NodeTypes.ELEMENT) {
    // 处理元素节点的子节点
    for (let i = 0; i < node.children.length; i++) {
      hoistStatic(node.children[i], context)
    }
  }
}

6.3 PatchFlags 优化

javascript
// PatchFlags常量定义
export const enum PatchFlags {
  // 表示节点有动态文本内容
  TEXT = 1,
  
  // 表示节点有动态class
  CLASS = 1 << 1,
  
  // 表示节点有动态style
  STYLE = 1 << 2,
  
  // 表示节点有动态props,但不包含class和style
  PROPS = 1 << 3,
  
  // 表示节点有动态key
  FULL_PROPS = 1 << 4,
  
  // 表示节点有动态插槽
  DYNAMIC_SLOTS = 1 << 5,
  
  // 表示需要完整比较子节点
  NEED_PATCH = 1 << 6,
  
  // 表示节点是静态的
  HOISTED = -1,
  
  // 表示是Fragment且子节点需要文本更新
  BAIL = -2
}

7. Composition API 实现

7.1 setup 函数处理

javascript
function handleSetupResult(instance: ComponentInternalInstance, setupResult: any) {
  if (isFunction(setupResult)) {
    // setup返回渲染函数
    instance.render = setupResult
  } else if (isObject(setupResult)) {
    // setup返回响应式对象
    instance.setupState = proxyRefs(setupResult)
  }
  
  // 完成组件设置
  finishComponentSetup(instance)
}

function finishComponentSetup(instance: ComponentInternalInstance) {
  const Component = instance.type as ComponentOptions
  
  // 如果没有render函数,从模板编译
  if (!instance.render) {
    if (compile && Component.template) {
      // 编译模板
      const { isCustomElement, compilerOptions } = instance.appContext.config
      const template = Component.template
      const render = compile(template, {
        isCustomElement,
        ...compilerOptions
      }).code
      
      instance.render = new Function(render)()
    }
  }
  
  // 应用选项式API
  if (!(__COMPAT__ && applyOptions(instance))) {
    // 处理选项式API
  }
}

7.2 生命周期钩子实现

javascript
// 注册生命周期钩子的函数
function injectHook(
  type: LifecycleHooks,
  hook: Function & { __weh?: Function },
  target: ComponentInternalInstance | null = currentInstance,
  prepend: boolean = false
): Function | undefined {
  if (target) {
    const hooks = target[type] || (target[type] = [])
    
    // 包装钩子函数
    const wrappedHook = hook.__weh || (hook.__weh = (...args: any[]) => {
      // 临时设置当前实例
      setCurrentInstance(target)
      
      // 执行钩子
      const res = args ? hook(...args) : hook()
      
      // 重置当前实例
      setCurrentInstance(null)
      
      return res
    })
    
    // 添加到钩子数组
    if (prepend) {
      hooks.unshift(wrappedHook)
    } else {
      hooks.push(wrappedHook)
    }
    
    return wrappedHook
  }
}

// 导出的生命周期函数
export function onMounted(fn: () => void) {
  injectHook(LifecycleHooks.MOUNTED, fn)
}

export function onUpdated(fn: () => void) {
  injectHook(LifecycleHooks.UPDATED, fn)
}

export function onUnmounted(fn: () => void) {
  injectHook(LifecycleHooks.UNMOUNTED, fn)
}

// 其他生命周期函数类似

8. 常见问题与优化技巧

8.1 性能优化关键点

  1. 响应式优化

    • 使用 shallowRefshallowReactive 避免深层响应式
    • 使用 markRaw 标记不需要响应式的数据
    javascript
    // 优化前
    const largeData = reactive({ /* 大量嵌套数据 */ })
    
    // 优化后
    const largeData = shallowReactive({ /* 只监听顶层属性 */ })
    const nonReactiveData = markRaw({ /* 完全不需要响应式 */ })
  2. 渲染优化

    • 使用 v-memo 缓存虚拟DOM节点
    • 正确使用 key 避免不必要的DOM操作
    • 使用 teleport 减少重渲染范围
    html
    <!-- 使用v-memo优化列表渲染 -->
    <div v-for="item in items" :key="item.id" v-memo="[item.id, item.name]">
      {{ item.name }}
    </div>
  3. 编译优化

    • 使用 v-once 标记静态内容
    • 避免在模板中使用复杂表达式
    • 使用 <template> 优化条件渲染
  4. 运行时优化

    • 使用 nextTick 批量更新DOM
    • 合理使用 computed 缓存计算结果
    • 避免在 watch 中进行昂贵操作

8.2 常见错误与解决方案

  1. 响应式数据无法触发更新
    • 问题:直接修改数组索引或长度
    • 解决方案:使用 pushsplice 等变异方法或 Vue.set
    javascript
    // 错误做法
    arr[index] = newValue
    arr.length = newLength

9. Vue 3 源码架构四大块分析

Vue 3源码架构主要分为四大核心模块,每个模块负责不同的功能,协同工作构成完整的Vue应用。

9.1 核心响应式系统 (@vue/reactivity)

核心功能

  • 实现数据响应式,自动追踪依赖和触发更新
  • 提供响应式API:reactive, ref, computed, watch
  • 支持Composition API的数据管理模式

主要组件

  • ReactiveEffect: 响应式副作用的核心实现
  • TrackOpTypes: 依赖收集操作类型
  • TriggerOpTypes: 触发更新操作类型
  • baseHandlers: 各类响应式处理器(get, set, deleteProperty等)

工作流程

数据 -> 响应式转换(reactive/ref) -> 依赖收集(track) -> 数据变化 -> 触发更新(trigger) -> 执行副作用(effect)

源码结构

@vue/reactivity
├── baseHandlers.ts    // 响应式处理器
├── computed.ts        // 计算属性实现
├── effect.ts          // 副作用系统
├── index.ts           // 导出API
├── reactive.ts        // 响应式对象转换
├── ref.ts             // 引用响应式
└── watch.ts           // 监听系统

9.2 运行时核心 (@vue/runtime-core)

核心功能

  • 虚拟DOM渲染系统
  • 组件系统实现
  • 生命周期管理
  • 调度器和更新队列

主要组件

  • renderer: 渲染器,负责DOM操作和更新
  • component: 组件实例创建和管理
  • vnode: 虚拟DOM节点定义
  • scheduler: 任务调度系统

工作流程

组件定义 -> 创建组件实例 -> 渲染函数执行 -> VNode创建 -> patch算法 -> DOM更新

源码结构

@vue/runtime-core
├── component.ts       // 组件系统
├── renderer.ts        // 渲染器核心
├── scheduler.ts       // 调度系统
├── vnode.ts           // 虚拟DOM定义
├── h.ts               // 创建VNode的函数
├── index.ts           // 导出API
└── modules/           // 各类模块(attrs, class, style等)

9.3 编译时系统 (@vue/compiler-core)

核心功能

  • 模板解析和转换
  • AST(抽象语法树)构建
  • 优化阶段(静态提升、PatchFlag等)
  • 代码生成

主要组件

  • parser: 模板解析器
  • transformer: AST转换器
  • optimizer: 静态分析和优化
  • codegen: 渲染函数生成器

工作流程

模板字符串 -> 解析器 -> AST -> 转换器 -> 优化器 -> 代码生成器 -> 渲染函数代码

源码结构

@vue/compiler-core
├── ast.ts             // AST节点定义
├── parser.ts          // 模板解析
├── transform.ts       // AST转换
├── codegen.ts         // 代码生成
├── index.ts           // 导出API
└── utils/             // 工具函数

9.4 服务端渲染 (@vue/server-renderer)

核心功能

  • 服务端预渲染Vue组件
  • 生成HTML字符串
  • 客户端水合(hydration)支持
  • 流式渲染优化

主要组件

  • renderToString: 同步渲染为HTML字符串
  • renderToNodeStream: 渲染为Node.js流
  • renderToWebStream: 渲染为Web Stream
  • hydration: 客户端水合逻辑

工作流程

组件 -> 服务端渲染 -> HTML字符串 -> 发送给客户端 -> 客户端水合 -> 交互应用

源码结构

@vue/server-renderer
├── index.ts           // 导出API
├── renderToString.ts  // 字符串渲染
├── renderToStream.ts  // 流式渲染
└── hydration.ts       // 水合逻辑

10. Vue 3 渲染流程详解

10.1 完整渲染流程

Vue 3的渲染过程从应用创建到DOM更新形成一个完整的闭环。以下是详细的渲染流程图和步骤解析:

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  1. 创建应用    │────>│  2. 挂载应用    │────>│  3. 渲染组件    │
│  createApp()    │     │  app.mount()    │     │  render()      │
└─────────────────┘     └─────────────────┘     └────────┬────────┘


┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  8. DOM更新完成 │<────│  7. 执行补丁    │<────│  6. 创建/更新   │
│  页面渲染       │     │  patch()        │     │  VNode          │
└─────────────────┘     └─────────────────┘     └────────┬────────┘


┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  4. 设置响应式  │<────│  5. 依赖收集    │     │  数据变化触发   │
│  数据           │     │  track()        │<────│  trigger()      │
└─────────────────┘     └─────────────────┘     └─────────────────┘

10.2 详细步骤解析

阶段1: 应用初始化

  1. createApp: 创建应用实例,初始化全局配置
  2. app.use(): 安装插件
  3. app.mount(): 挂载应用到DOM容器

阶段2: 组件渲染

  1. render函数调用: 执行组件的render函数
  2. VNode创建: 通过h函数/createVNode生成虚拟DOM树
  3. patch算法执行: 对比新旧VNode,计算更新差异
  4. DOM操作: 根据差异更新真实DOM

阶段3: 响应式更新

  1. 数据变化: 响应式数据被修改
  2. trigger触发: 通知相关依赖
  3. effect执行: 执行副作用函数
  4. 重新渲染: 组件重新渲染,进入阶段2

10.3 核心渲染流程图

下面是一个更详细的核心渲染流程说明:

javascript
// 1. 应用创建
const app = createApp({
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `<div>{{ count }}</div>`
})

// 2. 应用挂载
app.mount('#app')  // 内部执行: mount -> render -> patch

// 3. 首次渲染流程
// - 创建根组件实例
// - 执行setup函数
// - 响应式数据创建
// - 渲染函数执行
// - VNode树生成
// - 初始DOM渲染

// 4. 数据更新流程
count.value++  // 触发更新
// - setter调用
// - trigger通知依赖
// - scheduler调度更新
// - 组件重新渲染
// - 新VNode创建
// - patch算法执行
// - DOM更新

10.4 源码四大块协同工作

Vue 3的四大核心模块协同工作,形成完整的应用运行环境:

┌───────────────────────────────────────────────────────┐
│                    应用代码                           │
└───────────────┬──────────────────────┬────────────────┘
                │                      │
┌───────────────▼────────┐ ┌──────────▼─────────────────┐
│   编译时系统           │ │     运行时核心            │
│ @vue/compiler-core     │ │   @vue/runtime-core       │
│ - 模板解析             │ │ - 虚拟DOM渲染             │
│ - AST转换              │ │ - 组件系统                │
│ - 代码生成             │ │ - 生命周期                │
└───────────────┬────────┘ └──────────┬─────────────────┘
                │                     │
                │                     ▼
                │            ┌───────────────────┐
                │            │   服务端渲染      │
                │            │ @vue/server-renderer│
                │            │ - SSR渲染         │
                │            │ - 客户端水合      │
                │            └───────────────────┘
                │                     ▲
                ▼                     │
┌─────────────────────────────────────┴─────────────┐
│               响应式系统                          │
│           @vue/reactivity                         │
│ - 响应式数据转换                                  │
│ - 依赖收集与触发                                  │
│ - computed和watch                                 │
└───────────────────────────────────────────────────┘

11. 总结与面试重点

11.1 核心技术要点

  1. 组合式API设计: 更灵活的逻辑复用和代码组织
  2. 响应式系统重写: 基于Proxy的更精确响应式
  3. 虚拟DOM优化: PatchFlag、Block Tree等性能提升
  4. 模块化架构: 核心模块分离,按需引入
  5. TypeScript支持: 全面的类型定义

11.2 面试高频问题

  1. Vue 3相比Vue 2的主要改进: 响应式系统、组合式API、虚拟DOM优化等
  2. Composition API vs Options API: 各自优缺点和适用场景
  3. 响应式原理: Proxy的优势、Ref和Reactive的区别
  4. 虚拟DOM优化: PatchFlag、Block Tree的工作原理
  5. 生命周期钩子变化: 新增钩子和使用方式
  6. 性能优化策略: 静态提升、缓存、响应式优化等

11.3 技术亮点回顾

  • 更小的打包体积: 模块化设计和tree-shaking友好
  • 更高的渲染性能: 虚拟DOM优化和编译器改进
  • 更好的开发体验: TypeScript支持、组合式API的灵活性
  • 更强的扩展性: 插件系统和自定义渲染器
  • 更优的TypeScript集成: 内置类型定义和推导

通过深入理解Vue 3的源码实现,可以更好地掌握前端框架的设计思想,提升开发能力和问题排查效率。Vue 3的模块化架构和优化策略也为我们自己的项目开发提供了很好的参考。 // 正确做法 arr.splice(index, 1, newValue)


2. **setup中无法访问this**
- 问题:在setup函数中尝试使用this
- 解决方案:使用 `getCurrentInstance()` 获取实例或通过参数访问props和context
```javascript
// 错误做法
setup() {
  this.method() // 无效
}

// 正确做法
setup(props, { emit }) {
  // 使用props和emit
}
  1. ref在模板外需要.value

    • 问题:在JS中使用ref值时忘记加.value
    • 解决方案:在模板外访问时总是加上.value
    javascript
    const count = ref(0)
    
    function increment() {
      count.value++ // 必须加.value
    }
  2. 异步组件加载问题

    • 问题:使用动态导入但配置不正确
    • 解决方案:正确配置异步组件
    javascript
    // 正确的异步组件配置
    const AsyncComponent = defineAsyncComponent({
      loader: () => import('./MyComponent.vue'),
      loadingComponent: LoadingComponent,
      errorComponent: ErrorComponent,
      delay: 200,
      timeout: 3000
    })

9. Vue 3 面试重点记忆点

9.1 核心概念速记

  1. Composition API vs Options API

    • Composition API: 基于函数,更好的逻辑复用,适合复杂组件
    • Options API: 基于对象,易于理解,适合简单组件
  2. 响应式系统升级

    • Vue 2: Object.defineProperty,无法检测新增/删除属性
    • Vue 3: Proxy,可监听更多操作,性能更好
  3. 虚拟DOM优化

    • 静态提升: 标记并缓存静态节点
    • PatchFlags: 精确标记更新内容,减少比对开销
    • 动态节点收集: 只比对动态节点
  4. 性能优化策略

    • 编译时优化: 静态分析,预编译
    • 运行时优化: Proxy,减少不必要的依赖追踪
    • 运行时优化: 缓存机制,避免重复计算

9.2 重要API与用法

  1. 响应式API

    • ref: 创建基本类型响应式数据
    • reactive: 创建对象类型响应式数据
    • computed: 创建计算属性
    • watch: 监听数据变化
    • watchEffect: 副作用监听
  2. 生命周期钩子

    • onMounted: 组件挂载后
    • onUpdated: 组件更新后
    • onUnmounted: 组件卸载前
    • onBeforeUnmount: 组件卸载前
    • onErrorCaptured: 错误捕获
  3. 组件相关API

    • defineComponent: 定义组件
    • defineAsyncComponent: 定义异步组件
    • h: 创建虚拟节点
    • render: 渲染函数

9.3 面试题背诵要点

  1. Vue 3 相比 Vue 2 的主要改进

    • 更好的性能: 编译优化、运行时优化
    • 更小的包体积: 模块化设计,按需引入
    • Composition API: 更好的逻辑复用和类型推导
    • 更好的TypeScript支持
    • 新特性: Teleport、Fragments、Suspense等
  2. Proxy 相比 Object.defineProperty 的优势

    • 可监听数组索引和长度变化
    • 可监听新增/删除属性
    • 可监听Map/Set等集合类型
    • 性能更好,特别是在大数据量时
  3. Composition API 的设计思想

    • 函数式编程: 基于函数的API设计
    • 逻辑复用: 通过组合函数复用逻辑
    • 类型推导: 更好的TypeScript支持
    • 代码组织: 相关逻辑可以放在一起
  4. Vue 3 的编译优化

    • 静态节点提升: 减少重复创建VNode
    • PatchFlag: 精准定位更新内容
    • 缓存内联事件处理函数
    • 预字符串化静态内容

10. Vue 3 核心特性与最佳实践

10.1 组合式函数 (Composables)

javascript
// 示例: useCounter组合式函数
function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  return {
    count,
    increment,
    decrement,
    reset
  }
}

// 在组件中使用
const { count, increment } = useCounter()

10.2 响应式系统高级用法

javascript
// 深度监听
watch(
  () => user.profile.address,
  (newAddress) => {
    // 处理地址变化
  },
  { deep: true }
)

// 立即执行
watch(
  () => user.id,
  (id) => {
    fetchUserData(id)
  },
  { immediate: true }
)

// 监听多个源
watch([foo, bar], ([newFoo, newBar], [oldFoo, oldBar]) => {
  // 处理多个值的变化
})

10.3 自定义指令开发

javascript
// 全局指令
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

// 局部指令
const app = {
  directives: {
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}

10.4 插件开发

javascript
// 创建插件
const MyPlugin = {
  install(app, options) {
    // 添加全局属性
    app.config.globalProperties.$myMethod = () => {}
    
    // 添加全局资源
    app.directive('my-directive', {})
    
    // 注入依赖
    app.provide('myPlugin', options)
  }
}

// 使用插件
app.use(MyPlugin, { /* 选项 */ })

11. Vue 3 优缺点分析

11.1 优点

  1. 性能提升

    • 响应式系统更快
    • 虚拟DOM更高效
    • 更小的包体积
  2. 开发体验

    • 更好的TypeScript支持
    • Composition API提供更灵活的代码组织
    • 更多的现代特性支持
  3. 可维护性

    • 更好的逻辑复用机制
    • 更清晰的代码结构
    • 更好的类型推导

11.2 缺点

  1. 学习曲线

    • Composition API需要更多的函数式编程知识
    • 新概念和API增加了学习成本
  2. 生态成熟度

    • 部分第三方库可能还在适配中
    • 社区资源相对Vue 2较少
  3. 迁移成本

    • 从Vue 2迁移需要一定的工作量
    • 某些旧的开发模式不再推荐使用

12. 面试重点记忆口诀

12.1 响应式系统口诀

Proxy代理拦截,getter收集依赖
setter触发更新,effect管理副作用
ref包装基本值,computed缓存结果
watch观察变化,nextTick异步更新

12.2 组件生命周期口诀

setup初始化,beforeMount挂载前
mounted已挂载,beforeUpdate更新前
updated已更新,beforeUnmount卸载前
unmounted已卸载,errorCaptured捕获错

12.3 性能优化口诀

静态内容用v-once,复杂计算用computed
响应式数据要合理,大数据量用shallow
列表渲染加key值,条件渲染用template
异步更新用nextTick,DOM操作要批处理

12.4 编译优化口诀

静态节点要提升,patchFlag精确比对
事件处理需缓存,动态属性要标记
模板内容预编译,运行时效率更高
v-memo减少渲染,v-for避免内联函数

通过本章节的深入解析,相信你已经对Vue 3的源码实现有了全面的理解。Vue 3通过模块化设计、响应式系统升级、虚拟DOM优化等一系列改进,提供了更好的性能和开发体验。在实际开发中,合理利用这些特性和最佳实践,可以大幅提升应用的性能和可维护性。

Updated at:

Released under the MIT License.