vue3源码解析(一)

1,494 阅读12分钟

我们一般创建一个vue3项目会从createApp或createSSRApp开始,createSSRApp是服务端渲染的入口,createApp是客户端渲染的入口。

1.createApp 、 createSSRApp 初始化App实例

createApp

export const createApp = ((...args) => {
  // 创建基础应用实例(来自 runtime-core)
  const app = ensureRenderer().createApp(...args)

  // 开发环境注入校验逻辑
  if (__DEV__) {
    injectNativeTagCheck(app) // ← 校验原生 HTML/SVG 标签
    injectCompilerOptionsCheck(app) // ← 检查编译器选项有效性
  }

  // 重写 mount 方法(DOM 平台特有逻辑)
  const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // 标准化容器(支持选择器字符串)
    const container = normalizeContainer(containerOrSelector)
    if (!container) return

    // 处理无 render/template 的组件
    const component = app._component
    if (!isFunction(component) && !component.render && !component.template) {
      // 使用容器内 HTML 作为模板(安全警告)
      component.template = container.innerHTML // ← __UNSAFE__ 标记
    
      // Vue 2.x 兼容性检查(v- 指令在容器上的使用)
      if (__COMPAT__ && __DEV__) {
        // 遍历容器属性检查 Vue 指令
        for (let i = 0; i < container.attributes.length; i++) {
          const attr = container.attributes[i]
          if (/^(v-|:|@)/.test(attr.name)) {
            compatUtils.warnDeprecation(...) // ← 兼容性警告
          }
        }
      }
    }

    // 清空容器内容
    container.innerHTML = ''
    // 调用原始 mount(来自 runtime-core)
    const proxy = mount(container, false, resolveRootNamespace(container))
    
    // DOM 特定标记处理
    if (container instanceof Element) {
      container.removeAttribute('v-cloak') // ← 移除加载占位符
      container.setAttribute('data-v-app', '') // ← 应用挂载标识
    }
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

createSSRApp 与普通createApp的区别 :

功能模块createAppcreateSSRApp
渲染器类型客户端渲染hydration渲染器
mount参数(container, false)(container, true)
初始HTML处理清空容器内容保留服务端渲染的HTML
指令初始化普通指令SSR专用指令初始化
export const createSSRApp = ((...args) => {
  // 使用hydration渲染器创建应用实例
  const app = ensureHydrationRenderer().createApp(...args)

  // 开发环境校验逻辑
  if (__DEV__) {
    injectNativeTagCheck(app)  // ← 校验原生标签命名规范
    injectCompilerOptionsCheck(app) // ← 检查编译器配置有效性
  }

  // 重写mount方法(SSR专用逻辑)
  const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // 标准化容器处理
    const container = normalizeContainer(containerOrSelector)
    if (container) {
      // 调用核心mount方法,第二个参数true表示hydrate模式
      return mount(container, true, resolveRootNamespace(container))
    }
  }

  return app
}) as CreateAppFunction<Element>

1.ensureRenderer

其中ensureRenderer集成的是baseCreateRenderer方法。baseCreateRenderer 函数作为Vue的渲染核心,处理虚拟DOM到真实DOM的转换和更新,支持多种节点类型和优化策略

function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions,
): any {
    ...
    //从options中解构出平台相关的DOM操作方法,这使得渲染器可以跨平台使用。
    // 具体方法定义在nodeOps文件中
    const {
      insert: hostInsert,
      remove: hostRemove,
      patchProp: hostPatchProp,
      // ...其他方法
    } = options
    
    //这是虚拟DOM对比的核心算法,处理不同类型的节点更新逻辑。
    const patch: PatchFn = (n1, n2, container, anchor = null, ...) => {
      // 对比算法入口
      if (n1 && !isSameVNodeType(n1, n2)) {
        // 卸载旧节点
        unmount(n1, ...)
      }

      switch(n2.type) {
        case Text: 
          processText(...)
        case Comment:
          processCommentNode(...)
        // 其他节点类型处理
      }
    }
    
    ...
    
    //完整的元素挂载和更新逻辑,包含DOM操作和属性处理
    const processElement = (...) => {
      if (n1 == null) {
        mountElement(...)  // 首次挂载
      } else {
        patchElement(...)  // 更新
      }
    }
  
    const mountElement = (...) => {
      // 创建DOM元素
      el = hostCreateElement(...)
      // 处理children
      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        hostSetElementText(...)
      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        mountChildren(...)  // 递归处理子节点
      }
      // 处理props/指令/事件等
      if (props) {
        for (const key in props) {
          hostPatchProp(el, key, ...)
        }
      }
    }
    
    //处理组件的挂载和更新,包含组件实例的创建、生命周期调用等
    const processComponent = (...) => {
      if (n1 == null) {
        mountComponent(...)  // 挂载新组件
      } else {
        updateComponent(...) // 更新组件
      }
    }
    ...
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate),
      }
}

2.createAppAPI

createAppAPI是一个高阶函数,接收render和可选的hydrate函数作为参数,返回一个createApp函数

这个函数作为Vue应用构造的核心,实现了应用实例的创建、全局配置管理、插件系统、组件挂载等基础能力。

export function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>,
  hydrate?: RootHydrateFunction,
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    // 参数验证逻辑
    if (!isFunction(rootComponent)) {
      rootComponent = extend({}, rootComponent)
    }
    if (rootProps != null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }

    // 创建应用上下文
    const context = createAppContext()
    const installedPlugins = new WeakSet()

    let isMounted = false

    // 核心app实例对象
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null,
      _context: context,
      _instance: null,

      version,

      // 配置访问器
      get config() {
        return context.config
      },
      set config(v) {
        __DEV__ && warn(`app.config cannot be replaced.`)
      },

      // 插件安装方法
      use(plugin: Plugin, ...options: any[]) {
        if (installedPlugins.has(plugin)) {
          __DEV__ && warn(`Plugin has already been applied.`)
        } else if (plugin && isFunction(plugin.install)) {
          installedPlugins.add(plugin)
          plugin.install(app, ...options)
        } else if (isFunction(plugin)) {
          installedPlugins.add(plugin)
          plugin(app, ...options)
        }
        return app
      },

      // 全局混入
      mixin(mixin: ComponentOptions) {
        if (__FEATURE_OPTIONS_API__) {
          context.mixins.includes(mixin) || context.mixins.push(mixin)
        }
        return app
      },

      // 组件注册
      component(name: string, component?: Component): any {
        if (__DEV__) validateComponentName(name, context.config)
        if (!component) return context.components[name]
        context.components[name] = component
        return app
      },

      // 指令注册
      directive(name: string, directive?: Directive) {
        if (__DEV__) validateDirectiveName(name)
        if (!directive) return context.directives[name]
        context.directives[name] = directive
        return app
      },

      // 挂载核心逻辑
      mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        namespace?: boolean | ElementNamespace,
      ): any {
        if (!isMounted) {
          const vnode = createVNode(rootComponent, rootProps)
          vnode.appContext = context

          // 开发环境HMR支持
          if (__DEV__) {
            context.reload = () => {
              render(cloneVNode(vnode), rootContainer, namespace)
            }
          }

          // 执行实际渲染或hydration
          if (isHydrate && hydrate) {
            hydrate(vnode, rootContainer)
          } else {
            render(vnode, rootContainer, namespace)
          }

          isMounted = true
          app._container = rootContainer
          return vnode.component!.proxy
        }
      },

      // 卸载方法
      unmount() {
        if (isMounted) {
          render(null, app._container)
          delete app._container.__vue_app__
        }
      },

      // 依赖注入
      provide(key, value) {
        context.provides[key] = value
        return app
      },

      // 上下文保持
      runWithContext(fn) {
        const lastApp = currentApp
        currentApp = app
        try {
          return fn()
        } finally {
          currentApp = lastApp
        }
      }
    })

    return app
  }
}

3.流程图

graph TD
    subgraph createApp[createApp 客户端渲染]
        A1[初始化普通渲染器] --> B1[创建应用实例]
        B1 --> C1[开发环境校验]
        C1 --> D1[重写mount方法]
        D1 --> E1[清空容器内容]
        E1 --> F1[执行DOM渲染]
    end

    subgraph createSSRApp[createSSRApp 服务端渲染激活]
        A2[初始化hydration渲染器] --> B2[创建应用实例]
        B2 --> C2[开发环境校验]
        C2 --> D2[重写mount方法]
        D2 --> E2[保留SSR生成的HTML]
        E2 --> F2[执行hydration激活]
    end

    style createApp fill:#f9f9f9,stroke:#4a90e2
    style createSSRApp fill:#f9f9f9,stroke:#50b83c

2.mount

一般createApp完成后,我们会紧接着app.mount('#app')

const app = createApp(App);
// 全局方法挂载
app.config.globalProperties.useDict = useDict;
app.use(router);
app.use(store);
app.use(i18n);
app.use(plugins);
// 自定义指令
directive(app);
app.mount('#app');

在createApp中我们知道,会重写 mount 方法,我们就深入看下,mount里到底实现了什么

mount(
  rootContainer: HostElement,
  isHydrate?: boolean,
  namespace?: boolean | ElementNamespace,
): any {
  // 挂载状态检查(单例模式)
  if (!isMounted) {
    // 开发环境容器冲突检查(#5571问题修复)
    if (__DEV__ && (rootContainer as any).__vue_app__) {
      warn(`已有应用实例挂载到该容器`)
    }

    // 创建根组件虚拟节点
    const vnode = createVNode(rootComponent, rootProps)
    vnode.appContext = context // 注入应用上下文

    // 命名空间处理(SVG/MathML支持)
    if (namespace === true) namespace = 'svg'
    else if (namespace === false) namespace = undefined

    // 开发环境HMR支持
    if (__DEV__) {
      context.reload = () => {
        render(cloneVNode(vnode), rootContainer, namespace)
      }
    }

    // 核心渲染逻辑
    if (isHydrate && hydrate) {
    //SSR渲染
      hydrate(vnode as VNode<Node, Element>, rootContainer as any)
    } else {
        //普通渲染
      render(vnode, rootContainer, namespace)
    }

    // 更新挂载状态
    isMounted = true
    app._container = rootContainer
    // 容器标记(用于后续检测)
    ;(rootContainer as any).__vue_app__ = app

    // 开发工具集成
    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
      app._instance = vnode.component
      devtoolsInitApp(app, version)
    }

    // 返回组件代理对象
    return getExposeProxy(vnode.component!) || vnode.component!.proxy
  } else if (__DEV__) {
    warn(`应用已挂载,需创建新实例`)
  }
}

可以看出该方法实现了Vue应用实例挂载的核心流程,整合了虚拟DOM创建、渲染模式选择、开发环境支持等关键功能,是连接组件系统与渲染器的核心桥梁。

createVNode创建完成后会根据是否服务端渲染来进入render还是hydrate函数

下面我们着重分析各主要函数

1.createVNode

_createVNode是Vue中用于创建虚拟节点(VNode)的核心函数 主要负责包括参数处理、类型检查、兼容性处理、属性和样式的规范化,以及最终的通过调用createBaseVNode进行VNode创建过程

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false,
): VNode {
  // 核心处理流程分为5个阶段:
  // 1. 参数校验与默认值处理
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    if (__DEV__ && !type) {
      warn(`无效的vnode类型: ${type}`)
    }
    type = Comment // 降级为注释节点
  }

  // 2. 克隆现有VNode逻辑(处理<component :is="vnode"/>场景)
  if (isVNode(type)) {
    const cloned = cloneVNode(type, props, true) // 合并refs
    // ...子节点处理与块追踪逻辑...
    return cloned
  }

  // 3. 兼容性处理(Vue 2.x特性支持)
  if (isClassComponent(type)) {
    type = type.__vccOpts // 类组件选项标准化
  }
  
  // 当启用Vue 2兼容模式时
  if (__COMPAT__) {
    type = convertLegacyComponent(type, currentRenderingInstance) // 旧版组件转换
  }

  // 4. 属性与样式规范化
  if (props) {
    // 处理响应式属性副本
    props = guardReactiveProps(props)! 
    // class标准化(支持数组/对象语法)
    if (props.class && !isString(props.class)) {
      props.class = normalizeClass(props.class)
    }
    // style标准化(处理响应式样式对象)
    if (isObject(props.style)) {
      props.style = normalizeStyle(props.style)
    }
  }

  // 5. 类型标志计算(使用位运算优化性能)
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : isSuspense(type)
      ? ShapeFlags.SUSPENSE
      : isTeleport(type)
        ? ShapeFlags.TELEPORT
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT
            : 0

  // 开发环境响应式组件警告
  if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
    warn(`组件被意外响应式化,应使用markRaw标记`)
  }

  // 最终创建基础VNode
  return createBaseVNode(type, props, children, patchFlag, dynamicProps, shapeFlag, isBlockNode, true)
}

VNode多类型支持,使用 shapeFlag 位掩码快速判断节点类型

type VNodeTypes = 
  | string                // HTML标签
  | Component             // 组件对象
  | typeof Text            // 文本节点
  | typeof Fragment        // Fragment片段
  | typeof Teleport       // Teleport组件
  | typeof Suspense       // Suspense组件

1.cloneVNode

这个函数的主要职责是克隆VNode,合并额外的属性,处理ref和过渡效果

  • ref合并机制 通过数组结构合并多个ref引用,解决 < component :is > 场景下多个ref指向同一节点的问题。当 mergeRef=true 时会将新旧ref合并为数组

  • patchFlag优化策略 克隆时自动添加 FULL_PROPS 标志(Fragment除外),告知diff算法需要完整检查props变更。原始 HOISTED 节点升级为全属性检查

  • 作用域ID保留 scopeId 和 slotScopeIds 继承原始节点,确保克隆后的样式隔离和插槽作用域与原始节点一致

  • 动态内容处理 对 dynamicChildren 直接继承而非克隆,保持与原始block tree的关联,确保动态节点追踪的准确性

    我们可以通过克隆函数去了解Vnode节点个属性的含义

  export function cloneVNode<T, U>(
  vnode: VNode<T, U>,
  extraProps?: (Data & VNodeProps) | null,
  mergeRef = false,
  cloneTransition = false,
): VNode<T, U> {
  const { props, ref, patchFlag, children, transition } = vnode
  // 核心克隆逻辑分为5个阶段:
  // 1. 属性合并:安全合并原有props和额外props
   const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
   
   const cloned: VNode<T, U> = {
      __v_isVNode: true,      // 虚拟节点标识,用于快速判断对象类型
      __v_skip: true,         // 跳过响应式代理标记(VNode不需要响应式转换)

      type: vnode.type,        // 原始节点类型(组件/HTML标签/Symbol类型)

      props: mergedProps,      // 合并后的属性对象(包含原始props和额外添加的props)

      key: mergedProps && normalizeKey(mergedProps), // 标准化的key值(用于diff算法优化)
        // 引用处理,处理嵌套ref合并场景
      ref:  extraProps && extraProps.ref
        ? mergeRef && ref
          ? isArray(ref)
            ? ref.concat(normalizeRef(extraProps)!)
            : [ref, normalizeRef(extraProps)!]
          : normalizeRef(extraProps)
        : ref,     

      scopeId: vnode.scopeId, // 作用域ID(用于SFC样式隔离)

      slotScopeIds: vnode.slotScopeIds, // 插槽作用域ID(支持scoped slots)
        
        // 子节点克隆逻辑(开发环境深度克隆HOISTED节点)
      children: __DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
        ? (children as VNode[]).map(deepCloneVNode)
        : children, 

      target: vnode.target,    // Teleport目标容器(仅Teleport类型节点有效)

      targetAnchor: vnode.targetAnchor, // Teleport锚点位置

      staticCount: vnode.staticCount, // 静态节点数量(优化静态内容渲染)

      shapeFlag: vnode.shapeFlag, // 节点形状标志位(用位掩码表示节点类型)
        
       // 差异化更新标志(优化patch过程)
      patchFlag: extraProps && vnode.type !== Fragment
        ? patchFlag === PatchFlags.HOISTED // hoisted node
          ? PatchFlags.FULL_PROPS
          : patchFlag | PatchFlags.FULL_PROPS
        : patchFlag, 

      dynamicProps: vnode.dynamicProps, // 动态属性列表(优化props更新)

      dynamicChildren: vnode.dynamicChildren, // 动态子节点(block tree优化)

      appContext: vnode.appContext, // 应用上下文(全局配置/组件等)

      dirs: vnode.dirs,        // 指令绑定信息(包含生命周期钩子)

      transition: vnode.transition, // 过渡动画钩子(支持CSS/JS过渡)

      component: vnode.component,  // 组件实例(keep-alive缓存用)

      suspense: vnode.suspense, // Suspense边界状态(异步组件用)

      ssContent: /* 服务端渲染内容 */, // SSR激活内容

      ssFallback: /* 降级内容 */,     // SSR降级备用内容

      el: vnode.el,            // 对应真实DOM元素(渲染后赋值)

      anchor: vnode.anchor,     // Fragment锚点位置(DOM操作定位用)

      ctx: vnode.ctx,          // 渲染上下文(当前组件实例)

      ce: vnode.ce             // 自定义元素钩子(Web Components支持)
    }
  // 保持过渡效果与克隆节点的关联
  if (cloneTransition) {
    cloned.transition = vnode.transition?.clone(cloned)
  }
  __COMPAT__ && defineLegacyVNodeProperties(cloned)

  return cloned
}

patchFlag更新规则:

原始flag | 额外props         | 新flag
-------------------------------------------------
HOISTED  | 存在              | FULL_PROPS
其他     | 存在              | 原flag | FULL_PROPS
任意     | 不存在            | 保持原flag

兼容模式在该指令下有效 vue build --target lib --compat

2.createBaseVNode

createBaseVNode是创建虚拟节点的基础函数,参数包括类型、属性、子节点等。函数内部构造了一个vnode对象,并进行了多项处理,比如规范化子节点、验证key、跟踪block树(动态节点树)等

该函数是Vue虚拟DOM系统的基石,通过结构化数据封装和优化策略,为高效的组件渲染和更新机制提供核心支持。

    function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false,
) {
  // 核心构造流程分为三个阶段:
  const vnode = {
    __v_isVNode: true,      // 虚拟节点身份标识(与普通对象区分)
    __v_skip: true,         // 跳过响应式代理标记(优化性能)
    
    // 基础属性
    type,                   // 节点类型(组件/HTML标签/内置组件符号)
    props,                  // 合并后的属性对象
    key: props && normalizeKey(props), // 标准化后的key值
    ref: props && normalizeRef(props), // 处理后的ref引用
    
    // 作用域标识
    scopeId: currentScopeId, // 当前组件作用域ID(SFC样式隔离)
    slotScopeIds: null,     // 插槽作用域ID(动态插槽内容)
    
    // 子节点结构
    children,               // 原始子节点(后续进行规范化处理)
    
    // 组件相关
    component: null,        // 组件实例引用(挂载后赋值)
    suspense: null,         // Suspense边界状态(异步组件用)
    ssContent: null,        // SSR激活内容
    ssFallback: null,       // SSR降级内容
    
    // DOM关联
    el: null,               // 对应的真实DOM元素
    anchor: null,           // Fragment锚点位置
    target: null,           // Teleport目标容器
    targetAnchor: null,     // Teleport目标锚点
    
    // 优化标识
    staticCount: 0,         // 静态节点数量
    shapeFlag,              // 节点类型位掩码(快速类型判断)
    patchFlag,              // 差异化更新标志(优化diff算法)
    dynamicProps,           // 动态属性列表(优化props更新)
    dynamicChildren: null,  // 动态子节点(block tree优化)
    
    // 上下文信息
    appContext: null,       // 应用级配置(全局组件/指令等)
    ctx: currentRenderingInstance // 当前渲染实例上下文
  } as VNode

  // 阶段一:子节点规范化
  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children)  // 深度规范化所有子节点
    if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
      ;(type as typeof SuspenseImpl).normalize(vnode) // Suspense特殊处理
    }
  } else if (children) {
    vnode.shapeFlag |= isString(children)  // 快速设置文本/数组子节点标志
      ? ShapeFlags.TEXT_CHILDREN 
      : ShapeFlags.ARRAY_CHILDREN
  }

  // 阶段二:开发环境校验
  if (__DEV__) {
    if (vnode.key !== vnode.key) {  // 检测NaN类型的无效key
      warn(`创建了无效key的VNode,节点类型:`, vnode.type)
    }
  }

  // 阶段三:Block树追踪
  // isBlockTreeEnabled  控制Block树追踪层级的全局开关 > 0 : 启用动态节点收集(默认)  
  //isBlockNode 标识当前VNode是否为Block节点
  //  currentBlock  当前活跃的Block上下文                                                             
  if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
    const shouldTrack = (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
      vnode.patchFlag !== PatchFlags.NEED_HYDRATION
    if (shouldTrack) {  // 满足条件的动态节点加入当前block
      currentBlock.push(vnode)
    }
  }

  // 向后兼容处理
  if (__COMPAT__) {
    convertLegacyVModelProps(vnode)     // Vue2的v-model属性转换
    defineLegacyVNodeProperties(vnode)  // 添加Vue2风格的虚拟节点属性
  }

  return vnode
}
    
    // 形状标志(32位掩码)
enum ShapeFlags {
  ELEMENT = 1,
  COMPONENT = 1 << 2,
  SUSPENSE = 1 << 10
}

// 补丁标志(16位掩码) 
enum PatchFlags {
  TEXT = 1,        // 动态文本
  CLASS = 1 << 1,  // 动态class
  PROPS = 1 << 3   // 动态props(不含class/style)
}

3.blockTree与patchFlags

这两者相互作用来提升diff算法的效率

传统diff是需要一层一层的遍历,其中包括各种动态节点和静态内容,实际上静态内容无需比较

vue3引入了PatchFlag用来标识该vnode是动态节点,dynamicChildren属性来存放动态子节点,这种节点就是block节点,这样我们就可以知道那些节点是动态节点来进行靶向更新了。

具体的创建blockTree后续章节会讲到

vue3源码解析(二)