万字长文详细解析Vue 3 源码:从 createApp 到组件渲染

万字长文详细解析Vue 3 源码:从 createApp 到组件渲染

Vue 3 源码解析:从 createApp 到组件渲染

1. Vue 3 应用初始化

1.1 createApp 入口函数

1.1.1 入口函数的作用

createApp 是 Vue 3 应用的起点,它的核心任务是:

  1. 创建应用实例:基于根组件和根属性生成一个 Vue 应用实例。
  2. 初始化平台相关渲染器:确保渲染逻辑与当前环境(浏览器、小程序等)适配。
  3. 重写 mount 方法:标准化挂载流程,处理容器元素和初始渲染。

1.1.2 源码实现解析

源码位于 runtime-dom/src/index.ts(浏览器环境):

export const createApp = ((...args) => {
   
  // 1. 获取渲染器(Renderer)
  const renderer = ensureRenderer();
  
  // 2. 调用渲染器的 createApp 方法创建应用实例
  const app = renderer.createApp(...args);

  // 3. 保存原始 mount 方法
  const {
    mount } = app;

  // 4. 重写 mount 方法,处理容器标准化
  app.mount = (containerOrSelector: Element | string): any => {
   
    // 4.1 标准化容器
    const container = normalizeContainer(containerOrSelector);
    if (!container) return;

    // 4.2 清空容器内容(安全措施)
    container.innerHTML = '';

    // 4.3 调用原始 mount 方法进行挂载
    const proxy = mount(container, false, container instanceof SVGElement);
    
    return proxy;
  };

  return app;
}) as CreateAppFunction<Element>;
关键代码分析:
  1. ensureRenderer()
    创建或复用浏览器环境的渲染器,内部调用 createRenderer

    let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer;
    
    function ensureRenderer() {
         
      return renderer || (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions));
    }
    
    • rendererOptions 包含浏览器特有的 DOM 操作方法(如 insertremove)。
  2. renderer.createApp
    调用 runtime-core 中的 createAppAPI 生成应用实例:

    // runtime-core/src/apiCreateApp.ts
    export function createAppAPI<HostElement>(
      render: RootRenderFunction,
      hydrate?: RootHydrateFunction
    ): CreateAppFunction<HostElement> {
         
      return function createApp(rootComponent, rootProps = null) {
         
        // 创建应用上下文和插件集合
        const context = createAppContext();
        const installedPlugins = new Set();
    
        // 应用实例对象
        const app: App = {
         
          _component: rootComponent,
          _props: rootProps,
          _container: null,
          use(plugin: Plugin, ...options: any[]) {
          /* 插件安装逻辑 */ },
          component(name: string, component?: Component): any {
          /* 组件注册 */ },
          mount(rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean): any {
         
            // 核心挂载逻辑
          },
          // 其他方法(directive, unmount, provide 等)
        };
        return app;
      };
    }
    
  3. app.mount 的重写
    原始 mount 方法由 createAppAPI 定义,但平台可能需要额外处理容器。重写后的逻辑包括:

    • 标准化容器:支持传入选择器字符串或 DOM 元素。
    • 清空容器:避免遗留内容影响渲染。
    • 调用原始 mount:触发组件渲染。

1.2 应用实例的创建

1.2.1 应用实例的核心属性

应用实例(app)是 Vue 3 全局配置的载体,包含以下关键属性:

  • _component: 根组件对象(用户传入的 App.vue)。
  • _props: 根组件接收的初始属性。
  • _context: 应用上下文,提供全局配置(如组件、指令、混入等)。
  • _container: 挂载的 DOM 容器。

1.2.2 应用上下文的创建

createAppContext 定义应用全局状态:

// runtime-core/src/apiCreateApp.ts
export function createAppContext(): AppContext {
   
  return {
   
    app: null as any, // 由应用实例自身填充
    config: {
   
      isNativeTag: NO, // 默认非原生标签
      performance: false,
      globalProperties: {
   },
      optionMergeStrategies: {
   },
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: {
   }
    },
    mixins: [],
    components: {
   },
    directives: {
   },
    provides: Object.create(null)
  };
}
  • config: 全局配置,如自定义元素识别、性能监控开关。
  • mixins: 全局混入的选项。
  • components: 全局注册的组件(如 app.component('MyComp', ...))。
  • provides: 依赖注入的全局数据。

1.2.3 应用实例的方法

1. use():插件安装
use(plugin: Plugin, ...options: any[]) {
   
  if (installedPlugins.has(plugin)) {
   
    __DEV__ && warn(`Plugin has already been applied to target app.`);
  } 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;
}
  • 插件机制:通过 install 方法扩展应用功能(如 Vue Router、Vuex)。
2. component():全局组件注册
component(name: string, component?: Component): any {
   
  if (__DEV__) validateComponentName(name, context.config);
  if (!component) {
   
    return context.components[name];
  }
  context.components[name] = component;
  return app;
}
  • 注册逻辑:将组件存入 context.components,供模板解析使用。
3. mount():挂载根组件

原始 mount 方法的核心逻辑:

mount(rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean): any {
   
  // 1. 创建根组件的虚拟节点(VNode)
  const vnode = createVNode(rootComponent, rootProps);

  // 2. 存储应用上下文
  vnode.appContext = context;

  // 3. 调用渲染器的 render 方法
  render(vnode, rootContainer, isSVG);

  // 4. 标记容器为已挂载
  app._container = rootContainer;
  return vnode.component!.proxy;
}

1.3 全局 API 的实现

1.3.1 全局 API 的设计变化

Vue 3 废弃了 Vue.extendVue.mixin 等全局 API,改为通过应用实例配置,避免全局污染。

对比 Vue 2 的全局配置:
// Vue 2 的全局行为(影响所有实例)
Vue.component('my-component', {
    /* ... */ });
Vue.mixin({
    /* ... */ });

Vue 3 改为:

const app = createApp(App);
app.component('MyComponent', {
    /* ... */ });
app.mixin({
    /* ... */ });

1.3.2 全局属性注入

通过 app.config.globalProperties 添加全局属性:

// 用户代码
app.config.globalProperties.$http = axios;

// 组件内访问
this.$http.get('/api');

实现原理:在组件实例化时,将 globalProperties 合并到组件实例的上下文。

1.3.3 自定义元素与白名单

通过 app.config.isNativeTag 配置自定义元素:

// 告诉 Vue 将 <my-element> 视为原生元素
app.config.isNativeTag = (tag) => tag === 'my-element';

源码中的处理
在模板编译阶段,检查元素是否为原生标签或已注册组件。


1.4 挂载流程的完整解析

1.4.1 mount 方法的执行步骤

  1. 创建根组件的 VNode

    const vnode = createVNode(rootComponent, rootProps);
    
    • createVNode 是虚拟 DOM 的构造函数,标记节点类型、属子级等。
  2. 关联应用上下文

    vnode.appContext = context;
    
    • 所有子组件可通过 vnode.appContext 访问全局配置。
  3. 调用渲染器进行渲染

    render(vnode, rootContainer, isSVG);
    
    • render 函数位于 runtime-core/src/renderer.ts,核心逻辑为 patch 算法。
  4. 返回组件代理对象

    return vnode.component!.proxy;
    
    • proxy 是组件实例的公共接口,暴露属性方法供用户访问。

1.4.2 虚拟 DOM 的创建

createVNode 的简化实现:

// runtime-core/src/vnode.ts
export function createVNode(
  type: VNodeTypes,
  props: {
    [key: string]: any } | null = null,
  children: unknown = null
): VNode {
   
  const vnode: VNode = {
   
    __v_isVNode: true,
    type,
    props,
    key: props?.key,
    ref: props?.ref,
    // 其他属性(el, component, shapeFlag 等)
  };
  normalizeChildren(vnode, children); // 标准化子节点
  return vnode;
}
  • shapeFlag:标记节点类型(如元素、组件、插槽),优化后续 patch 过程。

1.4.3 渲染器的工作流程

渲染器的核心方法是 patch,负责将 VNode 转换为真实 DOM:

// runtime-core/src/renderer.ts
const patch = (
  n1: VNode | null, // 旧节点
  n2: VNode,         // 新节点
  container: HostElement,
  anchor: HostNode | null = null
) => {
   
  if (n1 === n2) return;

  const {
    type, shapeFlag } = n2;

  // 根据节点类型调用不同的处理函数
  switch (type) {
   
    case Text:
      processText(n1, n2, container, anchor);
      break;
    case Comment:
      processCommentNode(n1, n2, container, anchor);
      break;
    case Static:
      // 处理静态节点...
      break;
    default:
      if (shapeFlag & ShapeFlags.ELEMENT) {
   
        processElement(n1, n2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

prince_zxill

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值