搭建调试环境
为了弄清楚 Vue3 的初始化,倡议先克隆 Vue3 到本地。
git clone https://github.com/vuejs/vue-next.git
装置依赖
npm install
批改 package.json,将 dev 命令加上 --sourcemap
不便调试,并运行 npm run dev
// package.json..."scripts": { "dev": "node scripts/dev.js --sourcemap", ...}...
在 packages/vue 目录下减少 index.html,内容如下
<!-- index.html --><div id="app"> {{ count }}</div><script src="./dist/vue.global.js"></script><script> Vue.createApp({ setup() { const count = Vue.ref(0); return { count }; } }).mount('#app');</script>
在浏览器关上 index.html,程序失常运行则能够开始进行下一步调试。
进行调试
如果在上面的流程中迷失了方向,倡议先看一下结尾的总结,再回过头来看这一段。
在 createApp 的地位打上断点,而后刷新页面进断点,开始调试。
具体大家能够自行调试,我在这里就大略形容一下初始化流程。
createApp
进入 createApp 外部,会跳转到 packages/runtime-dom/src/index.ts 的 createApp,执行实现返回 app 实例。
// packages/runtime-dom/src/index.tsexport const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) ... return app}) as CreateAppFunction<Element>
接下来持续看 ensureRenderer 的实现。
// packages/runtime-dom/src/index.tsfunction ensureRenderer() { return ( renderer || (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)) )}
这里的 renderer 是个单例,初始时会调用 createRenderer 创立,再持续深刻。
来到 packages/runtime-core/src/renderer.ts,能够看到 createRenderer 又会调用 baseCreateRenderer。
// packages/runtime-core/src/renderer.tsexport function createRenderer< HostNode = RendererNode, HostElement = RendererElement>(options: RendererOptions<HostNode, HostElement>) { return baseCreateRenderer<HostNode, HostElement>(options)}
baseCreateRenderer 的实现有 2000 行,咱们只须要关注几个关键点就能够了。
其返回值也就是 ensureRenderer() 的返回值
// packages/runtime-core/src/renderer.tsfunction baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions): any { // 此处省略 2000 行 return { render, hydrate, createApp: createAppAPI(render, hydrate) }}
接下来回到开始的地位,在执行完 ensureRenderer() 后会接着执行 createApp,这个 createApp 就是上一步返回的 createApp,再接着看看做了哪些工作。
// packages/runtime-dom/src/index.tsexport const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) ... return app}) as CreateAppFunction<Element>
ensureRenderer 返回的 createApp 由 createAppAPI 实现,接下来再看看 createAppAPI 是如何实现的吧。
// packages/runtime-core/src/apiCreateApp.tsexport function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { const app: App = (context.app = { _uid: uid++, _component: rootComponent as ConcreteComponent, _props: rootProps, _container: null, _context: context, _instance: null, version, get config() {}, set config(v) {}, use(plugin: Plugin, ...options: any[]) {}, mixin(mixin: ComponentOptions) {}, component(name: string, component?: Component): any {}, directive(name: string, directive?: Directive) {}, mount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean ): any {}, unmount() {}, provide(key, value) {} }) return app }}
能够看到 createAppAPI 会返回一个 createApp 函数,也就是咱们调用 createApp,当 createApp 执行完之后会返回 app 实例。app 实例还会有 use、mixin、component、directive 等办法,能够为全局 app 增加一些扩大。
案例如下,传入的第一个参数为上一步的 rootComponent 也就是根组件。
// index.htmlconst app = Vue.createApp({}) .use(xxx) .component(xxx) .mount(xxx)
mount
createApp 创立 app 实例后,要渲染到页面上还须要调用 mount。接下来看看 mount 又做了什么工作。还是在 createAppAPI 外部
// packages/runtime-core/src/apiCreateApp.tsmount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean): any { if (!isMounted) { const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) ... if (isHydrate && hydrate) { hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else { render(vnode, rootContainer, isSVG) } isMounted = true app._container = rootContainer ... return vnode.component!.proxy } else if (__DEV__) { // 开发环境正告揭示,app 不能够反复挂载 }}
最终会执行 render(vnode, rootContainer, isSVG)
这一行代码,接下来看看调用 createAppAPI 时传入的 renderer。
回到 baseCreateRenderer 中,能够看到在 return 时调用 createAppAPI 传入的 renderer。
// packages/runtime-core/src/renderer.tsfunction baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions): any { // 此处省略 2000 行 return { render, hydrate, createApp: createAppAPI(render, hydrate) }}
其中 renderer 在 baseCreateRenderer 中定义了
const render: RootRenderFunction = (vnode, container, isSVG) => { if (vnode == null) { if (container._vnode) { unmount(container._vnode, null, null, true) } } else { patch(container._vnode || null, vnode, container, null, null, null, isSVG) } flushPostFlushCbs() container._vnode = vnode}
在 index.html 的例子中,第一次执行 render 时 vnode 是由 rootComponent
创立进去,rootComponent 则是 createApp 时传入的对象。
container
为 app 容器,也就是 id 为 app 的 div , container._vnode
为 undefined
。
所以最终会进入 patch 。
patch 的逻辑同样位于 baseCreateRenderer 中。代码太长了,这里就讲一下思路。在 patch 中会判断 vnode 的 type 或 shapeFlag 执行对应的操作。
因为第一次 patch 时,vnode 是一个组件,会进入 ShapeFlags.COMPONENT
的判断内,执行 processComponent
进行组件的解决。
而后会触发 mountComponent
挂载组件,从而触发 setupComponent(instance)
初始化组件的 props、slots、setup 等将须要 proxy 代理的数据做好筹备,以及将 template 进行编译为 render。
// packages/runtime-core/src/renderer.tsconst mountComponent: MountComponentFn = ( initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => { // 2.x compat may pre-creaate the component instance before actually // mounting const compatMountInstance = __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component const instance: ComponentInternalInstance = compatMountInstance || (initialVNode.component = createComponentInstance( initialVNode, parentComponent, parentSuspense )) ... // resolve props and slots for setup context if (!(__COMPAT__ && compatMountInstance)) { ... setupComponent(instance) ... } ... setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ) ...}
紧接着会执行 setupRenderEffect
,该办法会将渲染函数封装成一个副作用,当依赖的响应式数据发生变化时,会主动从新执行。
重点关注 componentUpdateFn
,代码太长了,这里也简略讲下吧,第一次会执行 instance.isMounted
为 undefined,则会进入创立流程,其中会执行 const subTree = (instance.subTree = renderComponentRoot(instance))
创立子树,而后通过 patch 递归创立子节点,完结后 instance.isMounted = true
。
一旦依赖发生变化,componentUpdateFn
会被从新执行,instance.isMounted
为 true,则会尽心更新的解决,具体就不再开展了。
至于为什么依赖发生变化 componentUpdateFn
会被从新执行,这个咱们留在下一篇文章中介绍,记得关注我。
总结
- 在 index.html 调用
createApp
时会先通过ensureRenderer
和baseCreateRenderer
生成上面的对象
// baseCreateRenderer 返回值return { render, hydrate, createApp: createAppAPI(render, hydrate)}
- 持续调用
baseCreateRenderer
返回的createApp
,这里的createApp
实际上调用的是createAppAPI
返回的函数。
createAppAPI
执行实现返回 app 实例。
- index.html 在创立好 app 后接着调用
mount
进行挂载,mount
的实现在createAppAPI
外部。
mount
执行时会调用 render
函数,该 render
在 baseCreateRenderer
传入。
render
则开始patch
进行渲染,patch
外部会进行递归渲染子节点。
以上就是 Vue3 的 createApp 和 mount 的大抵流程。至于第一步为什么须要通过 ensureRenderer
和 baseCreateRenderer
?
baseCreateRenderer
次要是平台无关的逻辑解决,寄存在 runtime-core 中。
当 patch
的时候须要操作 dom,则会调用内部传入的办法进行操作,这样就能够更不便实现跨端。
ensureRenderer
寄存在 runtime-dom 中,次要为 baseCreateRenderer
提供一系列 dom 操作的函数。
如果咱们要自定义渲染器,那么只须要实现ensureRenderer
即可。而不是像 Vue2 须要 fork 一份,大大提高了 Vue3 的利用范畴。
好了,这篇文章就水到这里吧。如有谬误的中央,心愿还能在评论区指出,感激!
下篇文章将解析 Vue3 的响应式原理,如果有趣味的话别忘了关注我呀,咱们一起学习、提高。