Vue核心运行时runtime-core之组件挂载流程

在Vue3的入口文件中,当我们在 main.js 中声明:

javascript 复制代码
createApp(App).mount('#app')

当调用mount时,实际上执行的是上一篇文章涉及的渲染器相关的render,如下:

javascript 复制代码
// App被解析为vnode
render(vnode, rootContainer)

一、从 render 到 patch

在render首次执行时,内部会执行

javascript 复制代码
patch(null, vnode, container)

因为是首次渲染,旧节点 n1 = null,patch 函数识别到这是一个组件类型 VNode,因为组件在解析成vnode时会绑定shapeFlag类型属性,判断时:

javascript 复制代码
if (shapeFlag & ShapeFlags.COMPONENT) {
       processComponent(
            n1, // null
            n2, //  vnode
            container,
            anchor
       )
}
javascript 复制代码
const processComponent = (
  n1,
  n2,
  container,
  anchor
) => {
  if (n1 == null) {//首次挂载
    mountComponent(
        n2,
        container,
        anchor,
        parentComponent
      );
  } else {
   // updateComponent(n1, n2); 组件更新非本文核心流程先绕过
  }
};

关键判断:n1 == null → 执行 挂载(mount) 而非更新

二、组件实例化与初始化

1.mountComponent时创建组件实例

javascript 复制代码
/*
* -组件实例创建函数
* @param vnode {object} -虚拟dom
* @returns instance {object} -组件实例
*/
function createComponentInstance(vnode) {
  const instance = {
        subTree: null, // 组件要渲染的子元素
        type, // 组件对象
        ctx: {}, // 组件的上下文
        props: {}, // 组件的属性
        attrs: {}, // 元素本身的属性
        slots: {}, // 组件的插槽
        setupState: {}, // 组件setup的返回值
        isMounted: false, // 组件是否被挂载?
        vnode
  }
  instance.ctx = {_: instance}
  return instance
}
const instance = createComponentInstance(vnode)

//创建一个组件实例对象(instance)
//初始化基础属性:
  vnode:当前 VNode
  type:组件定义(即 export default { ... })
  props、slots、ctx(上下文代理)
  isMounted = false
//此时还没有执行 setup
//instance会贯穿整个生命周期

三、启动setupComponent

javascript 复制代码
//这是组件实例初始化后的总入口,内部根据组件类型分发:
setupComponent(instance)
// 1.函数式组件调用: setupFunctionalComponent
// 2.有setup状态组件:setupStatefulComponent

本文以状态组件为主,因为平时大家开发的都是有状态的非函数式组件

javascript 复制代码
function setupStatefulComponent(instance) {
    // 通过创建proxy为后续实例数据更新做准备,非本文主要内容
    instance.proxy = new Proxy(instance.ctx,PublicInstanceProxyHandlers);
    const Component = instance.type;
    const {setup} = Component;
    if(setup){
        currentInstance = instance; // 保留当前实例
        const setupContext = createSetupContext(instance); // setup中的上下文,即ctx
        const setupResult = setup && setup(instance.props,setupContext);//在组件声明的setup函数中可访问对应参数
        handleSetupResult(instance,setupResult); // 处理返回值
        currentInstance = null;
    }else{
        finishComponentSetup(instance)
    }
}
function handleSetupResult(instance,setupResult){  
    if(isFunction(setupResult)){// 返回值可以是函数
        instance.render = setupResult
    }else if(isObject(setupResult)){ // 普通对象
        instance.setupState = setupResult
    }
    finishComponentSetup(instance)
}

此时 setup 已执行,但组件仍不能渲染------因为 render 函数还需最终确定

四、确认渲染函数 finishComponentSetup

javascript 复制代码
export function finishComponentSetup(instance, isSSR, skipOptions) {
  const Component = instance.type;

  // 模板 / render 函数标准化
  if (!instance.render) {
    // 非 SSR 且存在编译器、且组件未提供 render 函数时,尝试编译 template
    if (!isSSR && compile && !Component.render) {
      let template = Component.template
      
      if (template) { 
          Component.render = compile(template)
      }
      }
  }

    // 设置实例的 render 函数
    instance.render = Component.render || NOOP;
  }

  // 开发环境警告:缺少 template 或 render
  if (
    __DEV__ &&
    !Component.render &&
    instance.render === NOOP &&
    !isSSR
  ) {
    if (!compile && Component.template) {
      warn(
        `Component provided template option but ` +
          `runtime compilation is not supported in this build of Vue.` +
          (__ESM_BUNDLER__
            ? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".`
            : __ESM_BROWSER__
            ? ` Use "vue.esm-browser.js" instead.`
            : __GLOBAL__
            ? ` Use "vue.global.js" instead.`
            : ``)
      );
    } else {
      warn(`Component is missing template or render function: `, Component);
    }
  }
}

这是确保组件可渲染的关键一步。它按优先级确定

setup() 返回函数 > instance.render

至此,instance.render 具备了渲染VNode 的能力,具体渲染流程和实例响应式机制后续会文章更新

五、总结

组件的挂载是分阶段执行的,从初始化实例创建 到 setup 执行再到 render 确认,而render 函数来源灵活 支持 setup 返回函数、模板编译等多种方式,而了解组件实例的创建与渲染器的绑定是后续进行组件数据响应式更新的前提,后续也会陆续更新,欢迎大家在评论区讨论交流!

相关推荐
用户28320967937几秒前
从进程线程到 async/await:JS Event-loop事件循环机制底层解析
前端·javascript
岁月宁静9 分钟前
一、《从零手撸 Agent》 我用 10 行代码跑通了第一次大模型调用(顺便踩了 4 个坑)
前端·python·agent
guanguan0_012 分钟前
用 AI 做技术方案评审:输入 3 个方案,输出对比矩阵 + 推荐理由
javascript·人工智能·矩阵·ai编程
计算机魔术师28 分钟前
Fable 5.1 来了,智能体任务成本降 45%——Anthropic 这次为何降价这么狠
前端
Csvn29 分钟前
现代 CSS 新特性深度:container query、:has()、@scope 与 subgrid
前端
计算机魔术师34 分钟前
Anthropic 自己承认:模型越来越难管住了
前端
两只羊ovo42 分钟前
Agent 干活的底层地基:path 路径 + fs 文件系统,一次讲透
前端
xiaominlaopodaren42 分钟前
three.js地图视口瓦片(二):如何形成完整视口多边形
javascript·gis·three.js
一用书生43 分钟前
我给 ChatGPT、DeepSeek、Kimi 都加了一个「保存为笔记」按钮
前端·ai编程·vibecoding
咖啡无伴侣1 小时前
1. 从零搭建企业级 Monorepo 工程化模板:初始化、应用创建与共享 TypeScript 配置
前端·前端框架