文章首发:https://juejin.cn/post/6991461000033599495
笔者在上一篇文章《debug一下,你就学会高效浏览开源我的项目代码~》中以 Vue3 源码调试配置为案例,给大家介绍了调试的根本配置以及强调了其在查看源码过程中的重要性。本文将应用调试的技巧,在不须要具体理解残缺代码实现的前提下,疾速理解 Vue3 的执行流程。
最简略的代码
家喻户晓,调用 Vue.createApp
办法便创立了一个 Vue3 利用,本文从这个办法动手,一步一步分析该办法在源码中的实现,从而使得大家可能理解 Vue3 整体的执行流程。
首先,在源码目录创立 packages/vue/examples/hello.html
文件:
<script src="../dist/vue.global.js"></script><div id="demo">{{text}}</div><script> debugger Vue.createApp({ data: () => ({ text: 'hello world' }) }).mount('#demo')</script>
代码性能非常简单:在页面中打印 hello world
字符串。这里在执行 Vue.createApp
前插入 debugger
代码,使得代码执行在 debugger
处暂停下来。
调试开始
这里还是贴一下调试的配置,如需更具体的理解调试原理和流程,请拜访笔者上一篇文章:《debug一下,你就学会高效浏览开源我的项目代码~》。
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch hello", "url": "http://localhost:8080", "webRoot": "${workspaceFolder}", "file": "${workspaceFolder}/packages/vue/examples/hello.html" } ]}
点击调试按钮,程序在 debugger
处暂停,而后执行到 Vue.createApp
处单步进入,断点进入到 packages/runtime-dom/src/index.ts
中的 createApp
办法。
createApp
咱们间接来看 createApp
办法的源码,这里有局部代码删减,次要是针对 dev 环境的一些办法实现,不影响主体流程,下同。
// packages/runtime-dom/src/index.tsexport const createApp = ((...args) => { const app = ensureRenderer().createApp(...args) const { mount } = app app.mount = (containerOrSelector: Element | ShadowRoot | string): any => { const container = normalizeContainer(containerOrSelector) if (!container) return const component = app._component if (!isFunction(component) && !component.render && !component.template) { component.template = container.innerHTML } // clear content before mounting container.innerHTML = '' const proxy = mount(container, false, container instanceof SVGElement) if (container instanceof Element) { container.removeAttribute('v-cloak') container.setAttribute('data-v-app', '') } return proxy } return app}) as CreateAppFunction<Element>
暂且不看 const app = ensureRenderer().createApp(..args)
这行代码的外部实现,咱们间接单步跳过,在左侧的调试面板能够看到 app 的值如下所示:
那么咱们正当猜想,ensureRenderer().createApp(...args)
这行代码应用传进来的参数进行属性和办法初始化,并且挂载在 app 变量中返回。
返回 app
变量后,取出本来的 mount
办法,而后将一个新的办法实现挂载到 app
的 mount
属性中,也就是 html 文件中 .mount('#demo')
代码块的具体实现,最初返回 app 变量。
ensureRenderer().createApp(...args)
单步进入到 const app = ensureRenderer().createApp(...args)
这行代码如下所示:
这里波及到两个办法:ensureRenderer
和 createApp
,咱们一一来看。
ensureRenderer
// packages/runtime-dom/src/index.tsfunction ensureRenderer() { return ( renderer || (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)) )}
这个办法的外围是 createRenderer
,这里不开展这个办法的具体源码,咱们看这个办法返回值的定义:
// packages/runtime-core/src/renderer.tsexport interface Renderer<HostElement = RendererElement> { render: RootRenderFunction<HostElement> createApp: CreateAppFunction<HostElement>}
createRenderer
返回一个对象,具备 render
和 createApp
两个属性,render
是上面 mount
提到的渲染函数,这里不开展讲。createApp
办法是 createAppAPI
的返回值,源码如下所示:
export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { if (rootProps != null && !isObject(rootProps)) { __DEV__ && warn(`root props passed to app.mount() must be an object.`) rootProps = null } const context = createAppContext() const installedPlugins = new Set() let isMounted = false 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 }}
源码验证了咱们的猜想:createApp
将若干属性和办法挂载在 app
这个变量中,最初并返回 app
。
mount
在 const proxy = mount(container, false, container instanceof SVGElement)
这句代码中打一个断点并且单步进入,能够失去 mount
办法的代码实现:
// packages/runtime-core/src/apiCreateApp.tsmount( rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean): any { // 用一个变量来管制 mount 只执行一次 if (!isMounted) { // 创立一个虚构node节点 const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) // store app context on the root VNode. // this will be set on the root instance on initial mount. vnode.appContext = context // HMR root reload if (__DEV__) { context.reload = () => { render(cloneVNode(vnode), rootContainer, isSVG) } } if (isHydrate && hydrate) { hydrate(vnode as VNode<Node, Element>, rootContainer as any) } else { render(vnode, rootContainer, isSVG) } isMounted = true app._container = rootContainer // for devtools and telemetry ;(rootContainer as any).__vue_app__ = app return vnode.component!.proxy }}
能够看到,当咱们单步跳过 render(vnode, rootContainer, isSvg)
这行代码时,hello world
字符串显示在浏览器上了,也就是说,mount
办法将 Vue 组件挂载到浏览器上,而 render
则是要害的渲染办法。
这里有一个亮点是 mount
调用 createVNode
办法来创立虚构节点,render
接管虚构节点参数进行渲染,本文不再开展讲这一部分,有趣味能够关注笔者前面的文章。
总结
本文为笔者连载 Vue3 源码浏览的第一篇,不波及各种原理的解读,次要目标是在不须要浏览具体源码的前提下,疾速理解 Vue3 组件的执行流程。
心愿读者在通读本文之后,可能通过调试的形式自行高效地浏览 Vue3 源码,如果各位感觉本文能给大家带来帮忙,点个赞再走呗~~~