带你看Vue3源码- Vue.createApp究竟做了什么
给大家看一下Vue3的源码:Vue.createApp到底是干什么的?介绍调试的基本配置,强调其在查看源码过程中的重要性。本文将在不详细了解完整代码实现的情况下,通过调试技巧快速了解Vue3的执行过程。最简单的代码是众所周知的。调用Vue.createApp方法创建一个Vue3应用程序。本文从这个方法入手,一步步分析这个方法在源码中的实现,让大家了解Vue3的整体执行流程。首先,在源目录中创建packages/vue/examples/hello.html文件:{{text}}
代码功能为很简单:在页面上打印helloworld字符串。这里,在执行Vue.createApp之前插入了调试器代码,从而使代码执行在调试器处暂停。这里贴出调试配置。更详细的了解调试原理和过程,请访问作者之前的文章:《debug一下,你就学会高效阅读开源项目代码~》。{“版本”:“0.2.0”,“配置”:[{“类型”:“铬”,“请求”:“启动”,“名称”:“启动你好”,“网址”:“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.tsexportconstcreateApp=((...args)=>{constapp=ensureRenderer().createApp(...args)const{mount}=appapp.mount=(containerOrSelector:Element|ShadowRoot|string):any=>{constcontainer=normalizeContainer(containerOrSelector)if(!container)returnconstcomponent=app._componentif(!isFunction(component)&&!component.render&&!component.template){component.template=container.innerHTML}//在挂载之前清除内容container.innerHTML=''constproxy=mount(container,false,containerinstanceofSVGElement)if(containerinstanceofElement){container.removeAttribute('v-cloak')container.setAttribute('data-v-app','')}returnproxy}returnapp})asCreateAppFunction
不要看constapp=ensureRenderer().createApp(..args)这一行ofcode对于内部实现,我们直接跳过。在左侧的调试面板中,我们可以看到app的值如下:那么我们合理猜测一下,ensureRenderer().createApp(...args)这行代码使用传入的参数初始化属性和方法,并挂载到app变量中。返回app变量后,取出原来的挂载方法,然后挂载一个新的方法实现到app的mount属性中,即html中.mount('#demo')代码块的具体实现文件,app变量最终返回。ensureRenderer().createApp(...args)进入constapp=ensureRenderer().createApp(...args)这行代码如下:这里涉及两个方法:ensureRenderer和createApp,我们逐一一看。ensureRenderer//packages/runtime-dom/src/index.tsfunctionensureRenderer(){return(renderer||(renderer=createRenderer(rendererOptions)))}这个方法的核心是createRenderer,它将这里不做展开这个方法的具体源码,我们看一下这个方法返回值的定义://packages/runtime-core/src/renderer.tsexportinterfaceRenderer{render:RootRenderFunctioncreateApp:CreateAppFunction}createRenderer它返回一个具有两个属性的对象:render和createApp。render是下面mount中提到的渲染函数,这里不做赘述。createApp方法是createAppAPI的返回值,源码如下所示:exportfunctioncreateAppAPI(render:RootRenderFunction,hydrate?:RootHydrateFunction):CreateAppFunction{returnfunctioncreateApp(rootComponent,rootProps=null){if(rootProps!=null&&!isObject(rootProps)){__DEV__&&warn(`传递给app.mount()的根道具必须是一个对象。`)rootProps=null}constcontext=createAppContext()constinstalledPlugins=newSet()letisMounted=falseconstapp:App=(context.app={_uid:uid++,_component:rootComponentasConcreteComponent,_props:rootProps,_container:null,_context:context,_instance:null,version,getconfig(){},设置配置(v){},use(plugin:Plugin,...options:any[]){},mixin(mixin:ComponentOptions){},component(name:string,component?:Component):any{},指令(名称:字符串,指令?:指令){},安装(rootContainer:HostElement,isHydrate?:布尔值,isSVG?:布尔值):任何{},卸载(){},提供(键,值){}})returnapp}}源码验证了我们的猜测:createApp在app变量中挂载了几个属性和方法,最后返回appmount在代码constproxy=mount(container,false,containerinstanceofSVGElement)下打断点,步入其中,得到mount方法的代码实现://packages/runtime-core/src/apiCreateApp.tsmount(rootContainer:HostElement,isHydrate?:boolean,isSVG?:boolean):any{//使用变量控制挂载,只执行一次if(!isMounted){//创建虚拟节点constvnode=createVNode(rootComponentasConcreteComponent,rootProps)//在根VNode上存储应用上下文。//这将在初始挂载时设置在根实例上。vnode.appContext=context//HMRroot重新加载if(__DEV__){context.reload=()=>{render(cloneVNode(vnode),rootContainer,isSVG)}}if(isHydrate&&hydrate){hydrate(vnodeasVNode,rootContainerasany)}else{render(vnode,rootContainer,isSVG)}isMounted=trueapp._container=rootContainer//用于开发工具和遥测;(rootContainerasany).__vue_app__=appreturnvnode.component!.proxy}}可以看到当我们跨过render(vnode,rootContainer,isSvg)这行代码时,浏览器上显示的是helloworld字符串,也就是mount方法挂载Vue组件到浏览器,render是关键的渲染方法。这里有一个亮点就是mount调用createVNode方法创建虚拟节点,render接收虚拟节点参数进行渲染。本文不对这部分进行展开。感兴趣的可以关注作者后续文章。总结本文是作者系列Vue3源码阅读的第一篇文章。不涉及各种原理的解释。主要目的是在不阅读详细源码的情况下快速了解Vue3组件的执行过程。希望阅读本文的读者能够通过调试,高效地自行阅读Vue3源码。如果您觉得本文对您有帮助,请点个赞离开吧~~~