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 返回函数、模板编译等多种方式,而了解组件实例的创建与渲染器的绑定是后续进行组件数据响应式更新的前提,后续也会陆续更新,欢迎大家在评论区讨论交流!

相关推荐
Hello.Reader1 小时前
Qwik + Tauri 实战指南用静态导出把 Qwik 应用装进桌面应用里
前端·tauri
葡萄城技术团队1 小时前
SpreadJS 页眉页脚配置指南:占位符与奇偶页详解
前端
笨小孩丶1 小时前
前端性能优化实战:Map映射 vs 递归,差距210倍!
前端·性能优化·webworker·map映射·大数据渲染
daols881 小时前
如何使用 vue 甘特图 vxe-gantt 渲染显示多行任务,预计完成日期和实际完成日期多条任务条
vue.js·甘特图·vxe-gantt
小岛前端2 小时前
一文搞懂 SEO 全流程技术
前端
智塑未来2 小时前
卫星在轨运行5年以上用什么品牌SSD寿命够?航天级存储的长寿命保障技术解析
开发语言·javascript·数据库
柠檬豆腐脑2 小时前
Bun 全景指南:下一代 All-in-One 运行时详解与实战
前端·bun
悠闲蜗牛�2 小时前
零成本自建前端性能监控平台:从数据采集到可视化告警实战
前端
SuperEugene2 小时前
常见设计模式在 JS 里的轻量用法:单例、发布订阅、策略
前端·javascript·设计模式·面试