组件实例的创建与初始化 | 源码解析系列 3.3
一、引言
在Vue中,组件是核心概念。当我们使用createApp创建应用并挂载组件时,背后经历了复杂的初始化过程。理解组件实例的创建与初始化,对于深入理解Vue的运行机制至关重要。本文将深入解析Vue3中组件实例的创建过程,包括createComponentInstance、setupComponent等核心函数的实现。
二、组件实例概述
2.1 什么是组件实例
组件实例是Vue组件的运行时表示,它包含了组件的状态、属性、方法、生命周期等信息。每个组件实例都是独立的,有自己的响应式数据和作用域。
// 组件实例的结构constinstance={// 组件配置type:Component,// propsprops:{},// 非props属性attrs:{},// 插槽slots:{},// 组件上下文ctx:{},// 响应式数据data:{},setupState:{},// 计算属性computed:{},// 方法methods:{},// 生命周期钩子mounted:[],updated:[],// ...}2.2 组件创建的整体流程
组件从创建到渲染到挂载经历了以下主要阶段:VNode创建、组件实例创建、setup函数执行、render函数生成、DOM挂载。
// 整体流程// 1. 用户编写组件constApp={template:'<div>{{ message }}</div>',data(){return{message:'Hello'}}}// 2. 编译为渲染函数constApp={render(){returnh('div',{},this.message)}}// 3. 创建组件实例constinstance=createComponentInstance(vnode)// 4. 执行setupsetupComponent(instance)// 5. 执行渲染setupRenderEffect(instance)三、createComponentInstance
3.1 函数定义
createComponentInstance用于创建组件实例。
// packages/runtime-core/src/component.tsexportfunctioncreateComponentInstance(vnode:VNode,parent:ComponentInternalInstance|null,suspense:SuspenseBoundary|null):ComponentInternalInstance{// 创建实例对象constinstance:ComponentInternalInstance={// 唯一标识uid:uid++,// VNode引用vnode,// 组件类型type:vnode.typeasComponent,// 父实例parent,// 应用上下文appContext:nullasany,// propsprops:{},// attrsattrs:{},// 插槽slots:{},// 渲染函数render:null,// setup返回的渲染函数setupRenderEffect:null,// 响应式数据data:{},// setup返回的状态setupState:{},// 计算属性computed:{},// 方法methods:{},// 观察者watch:{},// 提供/注入provides:parent?Object.create(parent.provides):{},// 组件上下文ctx:{},// 生命周期钩子lifecycle:{},// 更新函数update:null,// 是否挂载isMounted:false,// 是否卸载isUnmounted:false,// ...}// 初始化ctxinstance.ctx=createRenderContext(instance)returninstance}3.2 组件实例的核心属性
组件实例包含多个核心属性。
// 核心属性说明interfaceComponentInternalInstance{// 标识uid:number// 唯一标识// 组件定义type:Component// 组件选项或setup函数vnode:VNode// 对应的VNode// 父子关系parent:ComponentInternalInstance|null// 父实例appContext:AppContext// 应用上下文// 数据props:Record<string,any>// 规范化后的propsattrs:Record<string,any>// 非props属性// 插槽slots:Record<string,Slot>// 插槽对象// 渲染render:InternalRenderFunction|null// 渲染函数setupRenderEffect:Function|null// 渲染effect// 响应式数据data:Record<string,any>// data()返回的数据setupState:Record<string,any>// setup返回的状态// 计算属性和方法computed:Record<string,ComputedRefImpl>// 计算属性methods:Record<string,Function>// 方法// 依赖注入provides:Record<string,any>// 提供的数据proxy:ComponentPublicInstance|null// 组件代理// 生命周期isMounted:boolean// 是否已挂载isUnmounted:boolean// 是否已卸载}四、createRenderContext
4.1 渲染上下文的作用
渲染上下文是组件实例对外暴露的API集合,它代理了组件的各个部分。
// 创建渲染上下文functioncreateRenderContext(instance:ComponentInternalInstance){// 初始化上下文对象constcontext={}asRecord<string,any>// 添加实例到上下文constinstanceProxy=newProxy(context,{// get拦截get(_,key){// 优先从setupState获取if(setupState[key]){returnsetupState[key]}// 然后从data获取if(data[key]){returndata[key]}// 从props获取if(props[key]){returnprops[key]}// 从计算属性获取if(computed[key]){returncomputed[key]}// 从方法获取if(methods[key]){returnmethods[key]}},// set拦截set(_,key,value){if(setupState[key]){setupState[key]=value}elseif(data[key]){data[key]=value}elseif(props[key]){// props只读,警告console.warn(...)}returntrue}})returninstanceProxy}4.2 上下文代理的处理
组件模板中使用的this实际上指向的是这个代理对象。
// 模板中使用 this.message// 编译后this.message// 实际上是通过代理访问instance.proxy.message五、setupComponent
5.1 setupComponent函数
setupComponent是初始化组件实例的核心函数。
// packages/runtime-core/src/component.tsexportfunctionsetupComponent(instance:ComponentInternalInstance,isSSR=false){// 获取组件的props定义const{props}=instance.type// 初始化propsinstance.props=props// 初始化slotsinstance.slots=normalizeSlots(instance.vnode.children||{},instance)// 执行setup函数setupStatefulComponent(instance,isSSR)}5.2 setupStatefulComponent
setupStatefulComponent负责执行setup函数并处理返回值。
// setupStatefulComponent实现functionsetupStatefulComponent(instance:ComponentInternalInstance,isSSR:boolean){// 获取组件类型constComponent=instance.typeasComponentOptions// 创建渲染上下文代理instance.proxy=newProxy(instance.ctx,PublicInstanceProxyHandlers)// 判断是否为setup函数constsetup=Component.setupif(setup){// 创建setup上下文constsetupContext=createSetupContext(instance)// 执行setup,获取返回值constsetupResult=setup(instance.props,// 只读的propssetupContext// 上下文对象)// 处理setup返回值handleSetupResult(instance,setupResult,isSSR)}else{// 没有setup,直接完成初始化finishComponentSetup(instance,isSSR)}}5.3 createSetupContext
创建setup函数的上下文对象。
// createSetupContext实现functioncreateSetupContext(instance:ComponentInternalInstance):SetupContext{return{// attrsattrs:instance.attrs,// slotsslots:instance.slots,// emitemit:(event:string,...args:any[])=>{// 调用实例的emit方法instance.emit(event,...args)},// exposeexpose:(exposed:Record<string,any>)=>{instance.exposed=exposed}}}六、handleSetupResult
6.1 处理setup返回值
setup函数可以返回对象或渲染函数。
// handleSetupResult实现functionhandleSetupResult(instance:ComponentInternalInstance,setupResult:SetupResult,isSSR:boolean){if(isFunction(setupResult)){// setup返回渲染函数instance.render=setupResult}elseif(isObject(setupResult)){// setup返回对象,作为响应式状态instance.setupState=proxyRefs(setupResult)}// 完成组件初始化finishComponentSetup(instance,isSSR)}6.2 proxyRefs
proxyRefs用于处理setup返回的ref。
// proxyRefs实现exportfunctionproxyRefs(objectWithRefs:any){returnisShallow(objectWithRefs)?objectWithRefs:newProxy(objectWithRefs,{get(target,key,receiver){// 自动解包refconstvalue=Reflect.get(target,key,receiver)returnisRef(value)?value.value:value},set(target,key,value,receiver){constoldValue=target[key]if(isRef(oldValue)){// ref直接设置valueoldValue.value=valuereturntrue}returnReflect.set(target,key,value,receiver)}})}七、finishComponentSetup
7.1 完成组件初始化
// finishComponentSetup实现functionfinishComponentSetup(instance:ComponentInternalInstance,isSSR:boolean){// 获取组件类型constComponent=instance.typeasComponentOptions// 如果没有render函数if(!instance.render){// 编译模板获取renderif(Component.template){Component.render=compile(Component.template,{isCustomElement:Component.isCustomElement,delimiters:Component.delimiters}asCompilerOptions)}}// 设置render函数instance.render=instance.render||function(){returnComponent.render}// 处理兼容选项式APIif(Component.data){instance.data=Component.data()}// 处理methodsif(Component.methods){instance.methods=Component.methods}// 处理computedif(Component.computed){instance.computed=Component.computed}}八、组件实例的挂载
8.1 setupRenderEffect
setupRenderEffect用于设置组件的渲染effect。
// setupRenderEffect实现functionsetupRenderEffect(instance:ComponentInternalInstance,initialVNode:VNode,container:HostElement,anchor:HostNode|null,parentSuspense:SuspenseBoundary|null,isMounted:boolean){// 创建响应式effectinstance.update=effect(functioncomponentEffect(){if(!instance.isMounted){// 首次挂载constsubTree=instance.render.call(instance.proxy,instance.proxy)patch(null,subTree,container,anchor,instance,parentSuspense)initialVNode.el=subTree.el instance.isMounted=true}else{// 更新constnextTree=instance.render.call(instance.proxy,instance.proxy)patch(instance.subTree,nextTree,container,anchor,instance,parentSuspense)}},{scheduler:queueJob})// 立即执行一次,完成首次挂载instance.update()}九、总结
本文深入解析了Vue3中组件实例的创建与初始化过程。createComponentInstance用于创建组件实例对象,setupComponent负责初始化props和slots,setupStatefulComponent执行setup函数并处理返回值,finishComponentSetup完成剩余的初始化工作,最后setupRenderEffect设置响应式渲染effect。
理解组件实例的创建过程对于深入掌握Vue的运行机制非常重要。后续我们将学习setup函数的执行机制、render函数的执行与追踪、以及组件挂载流程的详细内容。
参考资料
- Vue3 官方源码:https://github.com/vuejs/core
- Vue3 组件系统:packages/runtime-core/src/component.ts