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

相关推荐
默默学前端3 小时前
ES6模板语法与字符串处理详解
前端·ecmascript·es6
lxh01133 小时前
记忆函数 II 题解
前端·javascript
我不吃饼干3 小时前
TypeScript 类型体操练习笔记(三)
前端·typescript
华仔啊3 小时前
除了防抖和节流,还有哪些 JS 性能优化手段?
前端·javascript·vue.js
chenhdowue3 小时前
vue 表格 vxe-table 高亮行支持取消操作
vue.js·vxe-table
CHU7290353 小时前
随时随地学新知——线上网课教学小程序前端功能详解
前端·小程序
清粥油条可乐炸鸡3 小时前
motion入门教程
前端·css·react.js
这是个栗子3 小时前
【Vue3项目】电商前台项目(四)
前端·vue.js·pinia·表单校验·面包屑导航
前端Hardy4 小时前
Electrobun 正式登场:仅 12MB,JS 桌面开发迎来轻量化新方案!
前端·javascript·electron
树上有只程序猿4 小时前
新世界的入场券,不再只发给程序员
前端·人工智能