我们刚确认了分析流程
坏蛋Dan:vue runtime源码分析学习——day3:确定后续分析流程
当然,也有可能在不同地方使用不同方式分析。。。
createApp
这个方法在@vue/runtime-dom
[1]这个包里面
也是作为我们开发者项目的入口
在看代码之前先确定下测试用例
直接选择第一个就好,这会没有特殊要求
注意,这里在模拟client
的环境,document.createElement
就是client
的东西,只有jest.config
中配置的testEevironment
是jsdom
时才能使用,并且要搭配jsdom
这个包。
ensureRenderer
:这个方法我们放到下面去分析。这里看名字是创建一个renderer
也就是渲染器。injectNativeTagCheck
:这个方法代码就不看了,就是给这个app
的config
字段(这字段是一个对象),用Object.defineProperty
给它绑定一个isNativeTag
的方法,这个方法用来检测这个tag
是否是符合要求的,在这里app
就是一个组件,所以这里是用来检测app
的tag
是不是原生的tag
。原生tag
有两种:1. svg
: https://developer.mozilla.org/en-US/docs/Web/SVG/Element; 2. html
: https://developer.mozilla.org/en-US/docs/Web/HTML/Element。injectCompilerOptionsCheck
:这玩意儿是用来检测runtime-only
是否还在用compiler
相关的东西,比如app.config.compilerOptions
,这个是runtime-compiler
才会访问的,然后给出警告。注意injectCompilerOptionsCheck
和injectNativeTagCheck
都是dev only
的,也就是说prod
版本不会有检测。一般也不需要,因为如果你这组件的tag
不符合,在dev
阶段应该就已经测试出来了。至于为啥都要用Object.defineProperty
来绑定,大概是防止被覆盖掉了。重写set
或者writable
置为false
。
normalizeContainer
:这个代码也没啥好看的。如果你这个传入的selector
是一个string
,那么就调用document.querySelector
获取对应的dom
并返回,否则直接返回。这里面做的事情挺好理解的:
ensureRenderer
,生成渲染器并缓存createApp
,生成app
实例app
的mount
方法:dom
对象,也就是container
;dom
;2.x
写法,3.x
不支持在挂载对象上使用指令,3.x
中它被视为template
之外的部分;app
的mount
方法挂载到挂载对象上,并创建代理对象;v-cloak
[2],因为此时渲染完毕了;app
,在这里也就是我们的component
如果没有render function
,也没有template
,那么它会直接使用原来container
的innerHTML
作为它的template
。而ensureRenderer
、createApp
以及mount
三个方法我们接下来再分析。
baseCreateRenderer
开头三个名字一样的function
,之前有提到过这是typescript
提供的一种重载,可以支持不同参数。
baseCreateRenderer
这个方法非常非常长,有将近2000
行,所以这里就不贴出来了,到时候我们有遇到我们再具体分析。
这里我们要分析的是createApp
这个方法,而它使用了createAppApi
、render
这俩方法
我们先来看下createAppApi
这个createAppAPI
的方法是runtime-core
里的方法。
它返回一个createApp
的方法,这样render/hydrate
就会被缓存了。
createAppContext
:看下代码,记下这些字段,后面我们会遇到app
中的属性相信大家看着都眼熟,方法也是。
use
:这个相信大家都很熟悉,在我们使用vuex/vue-router
的时候就需要先app.use
才能使用,实际上这是在注册,而注册的规范是实现install
这个方法,类似webpack
需要插件实现apply
的方法。当然,install
不是必要的,如果你不想写对象的形式,直接传入一个函数也是可以的,它将作为install
方法注册安装到插件集合中。具体可以看官方的文档:Plugins | Vue.js (vuejs.org),注意这里把app
返回了,这也就是为什么可以链式调用的原因。mixin
:这个也是老熟人了,全局注册的mixin
会被应用于所有的组件,当组件create
的时候就会注册,而且是最先注册,要符合组件自身的mixin
优先级高的理念。当然,3.x
中已经不推荐了mixin
,只有在支持option api
的情况下才可以使用,关键key
就是__FETAURE_OPTIONS_API__
。component
:也是熟人,注册全局组件就是在这。directive
:同上,注册全局指令。unmount
:isMounted
是一个闭包缓存的标志位,用于判断是否已经渲染到html
中了。先调用render
方法将当前dom
在container
中注销。如果侦测到vue-devTool
,调用devtoolsUnmountApp
方法注销当前dom
在devtool
里面的注册。然后移除当前dom
的container
对当前dom
的引用,避免内存泄漏。provide
[3]: 就是将provide
的内容注册到context
中。这个context
贯穿整个app
,所以多深的组件多可以inject
到。installAppCompatProperties
:这个方法是用于兼容v2
的一些方法指令的,比如filter
等。这里就不看了,以后兼容性的代码我们就不看了。注意这里用的是createVNode
,而不是createVNodeCall
。这里创建的是一个VNode
节点,而我们之前分析的compiler-core
用的是createVNodeCall
,创建的是一个函数,函数调用后才会返回一个VNode
,辅助函数就是createVNode
。
然后这里使我们第一次在runtime
阶段遇到reload
,也就是热更新相关的功能,这个和我们正vue-loader
中发现的热更新代码是有关联的。
可以看到vue-loader
中的__VUE_HMR_RUNTIME__
是目标对象,它的reload
方法和我们这里的reload
应该是同一个,不过它没有接受参数,为什么呢?因为这个是根组件,它的reload
自然不需要确认是哪个组件。
render
方法我们先不分析,等下开个一级标题,内容较多。createVNode
:这个方法我们这里也不分析,放下面,就是基于VNodeCall
节点创建Vnode
。cloneVNode
:这个方法暂时就不看代码了,后面我们有空再说,简单的说就是将这个节点的所有数据都clone
一遍,注意是deep
的,当然,没有用JSON.stringify
这种操作,这种操作也是很危险的,因为有些数据在parse
回来会有问题。 这里采用的是递归处理。hydrate
:这个暂时不清楚是什么东西,mark
下。devtoolsInitApp
:就是将当前的app
组件注册到devTool
里面,这样能被vue-devTool
捕获,用于调试等。这个mount
方法是app
挂载的核心入口,这里核心就两件事:
vnodecall
也就是render function
转换成vnode
。vnode
通过patch
打补丁到html
上,方法是render
。这就是从js
变成html
的过程。
那么这个app
就创建好了。
等到createApp.mount
的时候,vnodecall
就变成vnode
然后再变成真实dom
。
这里我们暂时不看vnode
的生成也就是createVnode
方法里面做了什么,看一下一个vnode
结构即可。
记住这个vnode
的结构,我们开发的时候会经常遇到。
注意这里的unmount
,如果已经有_vnode
了,这个时候就意味着已经是patch
到浏览器上的真实dom
了。如果此时当前vnode
变成了null
,那么它就应该在真实dom
中被移除了。
如果没有直接patch
。
同样patch
方法我这里也不准备说,因为这个方法是核心之一,负责diff
,然后渲染。
flushPreFlushCbs
:代码就不看了,这里我们暂时还不知道是在做什么,后面如果直到了再贴代码分析。简单的说就是处理队列中有pre == true
属性的回调。flushPostFlushCbs
:这里同上一样不贴代码,简单的说就是将一些pending
状态的回调放到activePostFlushCbs
里面并且根据id
进行排序。如果此时还有active
的回调,那就return
,但是如果没有此时并没有,那就挨个处理。这里可以猜测是和$nextTick
有关的,每一个tick
都有回调需要处理,如果没处理完就放到一起下一个tick
再处理。最后再把vnode
重新赋值给container._vnode
,这个container
是真实dom
。
因为patch
的原因,此时vnode
已经被转换为真实dom
并渲染到浏览器上去了。
这里注意一点,container._vnode
这一步缓存的旧的vnode
,每次patch
都会被更新。
而patch
就是比对的这个container._vnode
和传入的最新的vnode
。
以前看别人的教程,都没有提到这一步,导致我一直很疑惑旧的vnode
从哪里来的。
今天分析的有些水,但createApp
大体流程应该是可以知道了的。
dom
,也就是我们需要挂载的app
的根节点,一般都是#app
。appContext
,这里面包含了一些api
,比如use、mount、umount
等。createApp
返回appContext
实例。use
方法中调用传入的插件的install
方法,然后将插件注册到全局的installPlugins
中。mount
方法中接收vnodecall也就是sfc
被处理成的render function
,在这里进行被转换成vnode
。然后调用render
方法。render
方法中调用patch
方法对新的vnode
进行diff
操作,这个操作涉及到真实dom
,此时已经从vnode
变成真实dom
了,也就是通过document.createElement
方法生成的dom
,并且被append/innerHTML
到浏览器上的container
里面。发布于 2023-02-17 15:32・IP 属地广东