前言:在前一篇文章《Vue源码之createApp组件的创建和mount的过程》中我们介绍了一个组件创建和挂载的过程,在这个过程中会创建组件实例并且初始化。我们都知道,在.vue文件中,可以在template内部编写模板代码,在运行的时候就自然会渲染到页面上。那么vue的模板是如何转换成真实dom的呢?让我们从源码的角度,亲身经历一下这个过程。
创建组件实例
创建组件实例的入口如下
在createComponentInstance内部,其实就是创建了一个instance对象,里面有很多属性,例如bc、c、bm等生命周期钩子,但是属性都是null(如下图所示)
创建完instance之后返回这个instance。
假如我们有下面这个组件,它的组件实例可以通过生命周期函数中打印this.$来查看
上面这个组件的组件实例如下:
初始化组件实例
初始化slots和props
创建完组件实例之后,调用setupComponent方法初始化组件实例。
在setupComponent方法内部先判断当前vnode是不是有状态组件,然后初始化props和slots。
执行setup
如果是有状态组件的话,会执行setupStatefulComponent方法来执行setup函数并获取结果setupResult,一般来说我们的setup返回的是一个对象,所以会进入handleSetupResult方法处理setupResult
在handleSetupResult方法内部,会根据setupResult的类型执行不同操作。通常我们的setup函数的返回值都是对象,所以会将setupResult保存在组件实例的setupState属性上。
然后执行finishComponentSetup来对组件初始化进行最后的一些处理。
组件模板转化成render函数
上面我们说到,在组件初始化最后,会执行finishComponentSetup来对组件初始化进行一些最后的处理,在执行这个函数的时候,内部执行了compile方法将模板转成对应的render函数,并挂载到组件实例的render属性中(详见前面的组件实例截图)。
(没用options api可以跳过)执行并设置生命周期并设置属性
如果在组件中使用了options API,在Vue3源码中会调用applyOptions方法对options API进行支持,如下所示。
而applyOptions方法内部,对options API的data,methods等属性进行了初始化,并且执行了生命周期钩子。下面我们观看applyOptions方法内部实现来理解“为什么在beforeCreate的时候data还不存在,只有created的时候才能使用data”。
在appyOptions方法内部,先取出了options APi的各种属性,包括生命周期钩子以及data等。
在执行所有逻辑之前,先调用了beforeCreate钩子
接着,对methods进行处理,通过bind修改this的绑定为publicThis,因此官方才提醒我们,methods不能写成箭头函数,因为箭头函数不能绑定this。
调用resolveData方法对data进行处理
而resolveData方法内部其实就是通过call方法调用data函数,所以从Vue3开始,options API中data必须是一个函数。将调用data函数获取的返回值加入响应式系统(reactive)并挂载到组件实例的data属性上(详见前面的组件实例截图)
其实还有对computed和watch等属性的处理,这里就不一一说明了。可以看到,所有属性都处理完之后,调用created生命周期钩子。
所以在created调用的时候,能获取到data内部的值。
接着就是设置其他的生命周期钩子(在合适的时机会回调,后文会介绍)
设置渲染有关的副作用函数
在初始化组件完成之后,调用setupRenderEffect来设置和渲染有关的副作用函数。
在setupRenderEffect函数内部会判断组件是否挂载,执行对应的操作
未挂载的情况
如果组件未挂载,会先执行beforeMount钩子
然后调用renderComponentRoot获取组件的subTree,也叫vnode,并保存在组件实例的subTree属性中(详见前面组件实例的截图)
renderComponentRoot是关键,后面我们会介绍
调用patch将subTree挂载到container中。
在patch挂载完subTree之后,执行mounted生命周期钩子。
然后标记为已挂载
已挂载的情况
如果是已挂载的情况,那么重新执行这个渲染有关的副作用函数的时候,就是更新组件的时候。
会先执行beforeUpdate(bu)生命周期钩子
重新调用renderComponentRoot获取更新后组件的新的vnode(nextTree),调用patch对新旧vnode进行diff算法(更新操作)
更新完成之后,调用updated(u)生命周期钩子
renderComponentRoot调用render函数获取vnode
在renderComponentRoot内部,其实就是调用了前面在组件实例中保存的render函数来生成vnode并且返回。
让我们来看一下文章最前面的组件生成的render函数,对于render函数的介绍可以查看我的另一篇文章《vue3的改变(hoist 与 Block)》。但是这里我们只需要知道,调用组件模板编译转化后生成的render函数,可以获取组件的vnode。
总结
整个过程可以简单的用下图来表示
在上面设置渲染有关的副作用函数中,在未挂载的情况里,使用patch将vnode挂载到container中;在已挂载的情况里,使用patch对新旧vnode进行更新操作。对于patch的具体实现,过两天会出一期新的文章来详细介绍。
如果觉得这篇文章让您对vue的理解有了进一步的提升,可以动动小手给个赞嘛呜呜呜(没人点赞呜呜呜)。如果有错误,可以在评论区指正!