Vue3初始化完整过程和原理

阶段 1:创建应用实例

js 复制代码
// 用户入口代码
const app = createApp(App);
app.mount('#app');
  1. createApp调用
    • 调用createApp创建应用实例,内部调用 ensureRenderer().createApp(...)
    • ensureRenderer ‌:创建或复用全局渲染器(包含核心的 patchnodeOps 等方法)。
    • createAppAPI ‌:生成应用实例对象,暴露 mountuse 等方法。

阶段 2:应用实例初始化

js 复制代码
// 内部执行 mount('#app') 
mount(rootContainer) { 
    // 步骤 1: 创建根组件 VNode 
    const vnode = createVNode(rootComponent, rootProps); 
    // 步骤 2: 调用核心渲染逻辑 
    render(vnode, rootContainer); 
}

创建根组件 VNode‌:

  • createVNode(rootComponent, rootProps)生成根组件的虚拟节点(VNode)。
  • VNode 包含组件配置(type)、props、children 等信息。

阶段 3:核心渲染逻辑(render 函数)

js 复制代码
// renderer.ts 中的 render 函数 
function render(vnode, container) { 
    if (vnode == null) { 
    // 卸载逻辑 if (container._vnode) unmount(container._vnode); 
    } else { 
    // 挂载或更新逻辑 
    patch(container._vnode || null, vnode, container); 
    } 
    // 缓存当前 
    VNode container._vnode = vnode; 
}
  1. 首次挂载 ‌:container._vnodenull,直接调用 patch(null, vnode, container)
  2. patch 函数 ‌:根据新旧 VNode 的差异进行 DOM 更新(首次挂载时旧 VNode 为 null)。

阶段 4:patch 过程(首次挂载)

js 复制代码
// patch 函数的分发逻辑 
const patch: PatchFn = (n1, n2, container, ...args) => {
    const { type } = n2; 
    if (type === Fragment) { 
        // 处理 Fragment 
    } else if (type === Text) { 
        // 处理文本节点 
    } else if (shapeFlag & ShapeFlags.COMPONENT) { 
        // 处理组件 
        processComponent(n1, n2, container, ...args); 
    } else if (shapeFlag & ShapeFlags.ELEMENT) {
        // 处理普通元素 
        processElement(n1, n2, container, ...args); 
    } 
};

4.1 处理根组件(processComponent

js 复制代码
const processComponent = (n1, n2, container) => { 
    if (!n1) { 
    // 首次挂载 mountComponent(n2, container); 
    } else {
    // 更新逻辑 updateComponent(n1, n2); 
    } 
};

阶段 5:挂载组件(mountComponent

js 复制代码
// mountComponent 核心逻辑 
function mountComponent(initialVNode, container) {
    // 步骤 1: 创建组件实例
    const instance = createComponentInstance(initialVNode); 
    // 步骤 2: 初始化组件实例 
    setupComponent(instance); 
    // 步骤 3: 设置渲染副作用(响应式依赖收集) 
    setupRenderEffect(instance, initialVNode, container); 
}

5.1 创建组件实例(createComponentInstance

  • 生成组件实例对象,包含以下关键属性:

    js 复制代码
    const instance = {
      type: vnode.type,         // 组件配置对象
      vnode: initialVNode,      // 当前 VNode
      props: {},                // 初始化 props
      attrs: {},                // 非 props 的 attributes
      slots: {},                // 插槽
      setupState: {},           // setup() 返回的状态
      ctx: {},                  // 渲染上下文(代理访问 setupState、props 等)
      render: null,             // 渲染函数
      isMounted: false,         // 是否已挂载
      subTree: null,            // 组件渲染的子树(VNode 树)
    };

5.2 初始化组件实例(setupComponent

js 复制代码
// setupComponent 核心逻辑
export function setupComponent(instance) {
  // 步骤 1: 初始化 props 和 slots
  initProps(instance, instance.vnode.props);
  initSlots(instance, instance.vnode.children);

  // 步骤 2: 执行有状态的 setup 函数
  const setupResult = instance.type.setup?.(
    instance.props,
    { attrs: instance.attrs, slots: instance.slots, emit: instance.emit }
  );

  // 步骤 3: 处理 setup 返回值
  if (isFunction(setupResult)) {
    // 返回渲染函数
    instance.render = setupResult;
  } else if (isObject(setupResult)) {
    // 将状态转换为响应式并挂载到实例
    instance.setupState = proxyRefs(setupResult);
  }

  // 步骤 4: 确定最终的 render 函数
  if (!instance.render) {
    instance.render = instance.type.render || NOOP;
  }
}

5.3 设置渲染副作用(setupRenderEffect

js 复制代码
// setupRenderEffect 核心逻辑
function setupRenderEffect(instance, initialVNode, container) {
  // 创建响应式副作用(effect)
  instance.update = effect(function componentEffect() {
    if (!instance.isMounted) {
      // 首次挂载
      // 步骤 1: 调用 beforeMount 生命周期钩子
      if (instance.type.beforeMount) {
        instance.type.beforeMount.call(instance.proxy);
      }

      // 步骤 2: 生成子树(subTree)
      const subTree = (instance.subTree = renderComponentRoot(instance));

      // 步骤 3: 递归 patch 子树到 DOM
      patch(null, subTree, container);

      // 步骤 4: 标记已挂载
      initialVNode.el = subTree.el;
      instance.isMounted = true;

      // 步骤 5: 调用 mounted 生命周期钩子
      if (instance.type.mounted) {
        instance.type.mounted.call(instance.proxy);
      }
    } else {
      // 更新逻辑(略)
    }
  }, { scheduler: queueJob });
}

阶段 6:递归处理子树(renderComponentRoot

js 复制代码
// 执行组件的 render 函数生成 subTree
function renderComponentRoot(instance) {
  const { render, proxy } = instance;
  let result;
  try {
    // 调用 render 函数,生成 VNode 树
    result = normalizeVNode(render.call(proxy));
  } catch (err) {
    // 错误处理
  }
  return result;
}
  1. render函数执行 ‌:
    • 若组件使用模板,render 函数由编译器生成(包含 _createBlock 等优化)。
    • 若组件手写 render 函数,直接返回 VNode。

阶段 7:处理元素(processElement

js 复制代码
// 处理普通元素(如 div、span)
const processElement = (n1, n2, container) => {
  if (!n1) {
    // 首次挂载元素
    mountElement(n2, container);
  } else {
    // 更新逻辑(略)
  }
};

// mountElement 核心逻辑
function mountElement(vnode, container) {
  // 步骤 1: 创建 DOM 元素
  const el = (vnode.el = hostCreateElement(vnode.type));

  // 步骤 2: 处理子节点(递归或文本)
  if (vnode.children) {
    if (typeof vnode.children === 'string') {
      // 文本子节点
      hostSetElementText(el, vnode.children);
    } else {
      // 子节点为 VNode 数组,递归 patch
      vnode.children.forEach(child => {
        patch(null, child, el);
      });
    }
  }

  // 步骤 3: 处理 props(包括事件、属性、类、样式等)
  if (vnode.props) {
    for (const key in vnode.props) {
      hostPatchProp(el, key, null, vnode.props[key]);
    }
  }

  // 步骤 4: 插入到父容器
  hostInsert(el, container);
}

阶段 8:处理动态内容(Block Tree 优化)

  1. createBlock函数‌:

    • 在编译阶段,模板中的动态节点(如 v-ifv-for)会被包裹在 createBlock 中。
    • Block 节点会通过 dynamicChildren 数组收集所有动态子节点。
    less 复制代码
    typescriptCopy Code
    // 编译后的 render 函数示例
    function render() {
      return (_openBlock(), _createBlock("div", null, [
        _createVNode("span", null, "Static"),
        _createVNode("span", { class: _ctx.dynamicClass }, _toDisplayString(_ctx.dynamicText), 1 /* TEXT */)
      ]))
    }
  2. 靶向更新‌:

    • patch 过程中,Block 节点的 dynamicChildren 会被直接遍历,仅处理动态节点。
    • 通过 patchFlags 标记动态类型(如 TEXTCLASS),跳过无关的 Diff 逻辑。

阶段 9:DOM 插入与完成挂载

  1. hostInsert方法 ‌:
    • 将创建好的 DOM 元素插入到父容器(如 #app)。
    • 最终,根组件的所有子节点递归挂载完毕,页面完成渲染。
相关推荐
同志327131 小时前
用HTML+CSS做了一个网易云音乐客户端首页
前端·css
小猪欧巴哟1 小时前
pnpm install 安装项目依赖遇到 illegal operation on a directory, symlink 问题
前端·vue.js
独角仙梦境1 小时前
🚀🚀🚀学习这个思路,你也能手撸自己的专属vip脚手架🚀🚀🚀
前端
CJWbiu1 小时前
Github Action + docker 实现自动化部署
前端·自动化运维
关山1 小时前
在TS中如何在子进程中动态实例化一个类
前端
吃瓜群众i1 小时前
兼容IE8浏览器的8个实用知识点
前端·javascript
前端烨1 小时前
vue3子传父——v-model辅助值传递
前端·vue3·组件传值
Mintopia2 小时前
Three.js 在数字孪生中的应用场景教学
前端·javascript·three.js
夕水2 小时前
自动化按需导入组件库的工具rust版本完成开源了
前端·rust·trae
JarvanMo3 小时前
借助FlutterFire CLI实现Flutter与Firebase的多环境配置
前端·flutter