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