当前位置: 首页 > Web前端 > HTML

源码分析vue3createApp是干什么的

时间:2023-03-28 19:59:39 HTML

我们使用createApp创建一个vueapp实例如下,会发生什么?const{createApp}=VuecreateApp({setup(){return{}}}).mount('#app')我们来看一下。首先进入vue导出的createApp函数,将所有参数合并到args中,调用ensureRenderer函数并在其返回数据上调用createApp,然后将args打散传入。这里可以看到ensureRenderer调用runtime-core中导出的createRenderer方法,createRenderer接收rendererOptions。这样做的目的是将渲染器用作单例缓存,以避免创建多个渲染器。二、vue3考虑了更好的多端渲染支持,没有和浏览器的dom操作强耦合,而是把一些操作的具体实现暴露给开发者自己实现,这里传入的rendererOptions就是浏览器dom环境的具体实现import{createRenderer}from'@vue/runtime-core'constrendererOptions=extend({patchProp},nodeOps)//一些渲染相关的配置,比如更新属性的方法,操作DOM的方法letrenderer:Renderer<元素|影根>|HydrationRendererfunctionensureRenderer(){return(renderer||(renderer=createRenderer(rendererOptions)))}exportconstcreateApp=((...args)=>{constapp=ensureRenderer().createApp(...args)////...})createRenderer方法内部调用baseCreateRendererexport函数(选项ns)}baseCreateRenderer有很多内部内容。这里的options是从外部传入的平台相关的操作细节。内容太长,此处省略。再往下,函数末尾导出了两个比较重要的东西,render方法,createApp方法,createApp方法是通过createAppAPI方法创建的,接受render和hydrate。这也是外部可以调用ensureRenderer().createApp(...args)的原因import{createAppAPI}from'./apiCreateApp'functionbaseCreateRenderer(options:RendererOptions,createHydrationFns?:typeofcreateHydrationFunctions):any{const{insert:hostInsert,移除:hostRemove,patchProp:hostPatchProp,createElement:hostCreateElement,createText:hostCreateText,createComment:hostCreateComment,setText:hostSetText,setElementText:hostSetElementText,parentNode:hostParentNode,nextSibling:hostNextSibling,setScopeId:hostSetScopeId=NOOP,cloneNode:hostCloneNode,insertStaticContent:hostInsertStaticContent}=optionsconstpatch:PatchFn=()=>{/*....*/}constprocessText:ProcessTextOrCommentFn==()=>{/*....*/}constprocessCommentNode:ProcessTextOrCommentFn==()=>{/*....*/}constmountStaticNode==()=>{/*....*/}constpatchStaticNode=()=>{/*....*/}constmoveStaticNode=()=>{/*....*/}constremoveStaticNode=()=>{/*....*/}constprocessElement=()=>{/*....*/}constmountElement=()=>{/*....*/}constsetScopeId=()=>{/*....*/}constmountChildren:MountChildrenFn=()=>{/*....*/}constpatchElement=()=>{/*....*/}//块的快速路径。constpatchBlockChildren:PatchBlockChildrenFn=()=>{/*....*/}constpatchProps=()=>{/*....*/}constprocessFragment=()=>{/*....*/}constprocessComponent=()=>{/*....*/}constmountComponent:MountComponentFn=()=>{/*....*/}constupdateComponent=()=>{/*。...*/}constsetupRenderEffect:SetupRenderEffectFn=()=>{/*....*/}constupdateComponentPreRender=()=>{/*....*/}constpatchUnkeyedChildren=()=>{/*....*/}constpatchKeyedChildren=()=>{/*....*/}constmove:MoveFn=()=>{/*....*/}constunmount:UnmountFn=()=>{/*....*/}constremove:RemoveFn=()=>{/*....*/}constremoveFragment=()=>{/*....*/}constunmountComponent=()=>{/*....*/}constunmountChildren:UnmountChildrenFn=()=>{/*....*/}constgetNextHostNode:NextFn=()=>{/*....*/}constrender:RootRenderFunction=()=>{/*....*/}constinternals:RendererInternals=()=>{/*....*/}return{render,hydrate,createApp:createAppAPI(render,hydrate)}}createAppAPI在内部返回createApp函数。该函数主要创建app对象的基本数据结构,具有uuid、use(注册插件)、component(注册子组件)、mount等属性和方法。这里需要注意的是,mount方法内部调用了createAppAPI接收到的renderer,此时以闭包的形式存在,最后返回app对象导出函数createAppAPI(render:RootRenderFunction,hydrate?:RootHydrateFunction):CreateAppFunction{returnfunctioncreateApp(rootComponent,rootProps=null){constcontext=createAppContext()constinstalledPlugins=newSet()letisMounted=falseconstapp:App=(context.app={_uid:uid++,_component:rootComponent作为ConcreteComponent,_props:rootProps,_container:null,_context:context,_instance:null,version,getconfig(){returncontext.config},use(plugin:Plugin,...options:any[]){if(installedPlugins.has(plugin)){__DEV__&&warn(`插件已经应用到目标应用程序。`)}elseif(plugin&&isFunction(plugin.install)){installedPlugins.add(plugin)plugin.install(app,...options)}elseif(isFunction(plugin)){installedPlugins.add(plugin)plugin(app,...options)}returnapp},mixin(mixin:ComponentOptions){如果(__FEATURE_OPTIONS_API__){if(!context.mixins.includes(mixin)){context.mixins.push(mixin)}}returnapp},//注册表组件component(name:string,component?:Component):any{if(!component){returncontext.components[name]}context.components[name]=componentreturnapp},directive(name:string,directive?:Directive){if(!directive){returncontext.directives[name]作为任何}context.directives[name]=指令returnapp},mount(rootContainer:HostElement,isHydrate?:boolean,isSVG?:boolean):any{if(!isMounted){constvnode=createVNode(rootComponentasConcreteComponent,rootProps)vnode.appContext=contextrender(vnode,rootContainer,isSVG)isMounted=trueapp._container=rootContainer;(rootContainerasany).__vue_app__=app//app在dom上挂起__vue_app__属性返回vnode.component!.proxy}},unmount(){if(isMounted){render(null,app._container)deleteapp._container.__vue_app__}},provide(key,value){context.provides[keyasstring]=valuereturnapp}})returnapp}}返回app后,返回原来的createApp函数,将返回的app实例存放在app变量中,取出mount方法,重新定义一个app。mount方法是一个包原来的挂载方法。封装的目的与渲染器的目的相同。平台的具体实现由开发者分离并实现。这里,app.mount方法接收到的containerOrSelector参数就是我们从外部传入的#app选择器,然后使用normalizeContainer规范这个选择器,返回实际的dom。如果没有找到实际的dom,则直接返回。如果组件不提供模板,render,它会以dom的innerHTML为模板,在调用mount方法挂载前清空实际dom的innerHTML,挂载后去掉v-cloak等属性,返回appexportconstcreateApp=((...args)=>{constapp=ensureRenderer().createApp(...args)const{mount}=app//移除原来的挂载app.mount=(containerOrSelector:Element|ShadowRoot|string):any=>{constcontainer=normalizeContainer(containerOrSelector)if(!container)returnconstcomponent=app._component//获取组件的对象数据if(!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当遇到问题时hod是外部调用的,app.mount自然是调用createApp().mount('#app')这就进入了createApp中定义的初始挂载,内部创建了一个vNode,并调用render方法进行渲染,修改isMountedmount(rootContainer:HostElement,isHydrate?:boolean,isSVG?:boolean):any{if(!isMounted){constvnode=createVNode(rootComponent作为ConcreteComponent,rootProps)vnode.appContext=contextrender(vnode,rootContainer,isSVG)isMounted=trueapp._container=rootContainer;(rootContainerasany).__vue_app__=appreturnvnode.component!.proxy}}好了,那么整个过程就结束了。让我们整理一下。整个调用过程如下:createApp->ensureRenderer->baseCreateRenderer->createAppAPI(接收render方法)->createApp创建并返回app对象->返回到createApp,打包app.mount->外部调用.mount('#app')->触发app.mount->mount->调用render方法渲染