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)。
    • 最终,根组件的所有子节点递归挂载完毕,页面完成渲染。
相关推荐
10年前端老司机4 分钟前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~6 分钟前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客1 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2451 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇6 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖6 小时前
http的缓存问题
前端·javascript·http
小小小小宇6 小时前
请求竞态问题统一封装
前端
loriloy6 小时前
前端资源帖
前端
源码超级联盟7 小时前
display的block和inline-block有什么区别
前端
GISer_Jing7 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js