当前位置: 首页 > Web前端 > vue.js

深入Vue3源码,学习初始化流程

时间:2023-04-01 02:11:11 vue.js

并搭建调试环境为了理解Vue3的初始化,建议先将Vue3克隆到本地。gitclonehttps://github.com/vuejs/vue-next.git安装依赖npminstall修改package.json,添加dev命令带--sourcemap方便调试,运行npmrundev//package.json//。.."scripts":{"dev":"nodescripts/dev.js--sourcemap",//...}//...在packages/vue目录下添加index.html,内容如下{{count}}

在浏览器中打开index.html,得到程序可以正常运行开始下一步调试。调试如果在后面的过程中迷路了,建议先看最后的总结,再回头看这段。在createApp的位置打断点,然后刷新页面进入断点开始调试。具体可以自己调试。这里我简单描述一下初始化过程。createApp进入createApp,跳转到packages/runtime-dom/src/index.ts中的createApp,执行完返回app实例。//packages/runtime-dom/src/index.tsexportconstcreateApp=((...args)=>{constapp=ensureRenderer().createApp(...args)//...returnapp})asCreateAppFunction接下来继续看ensureRenderer的实现。//packages/runtime-dom/src/index.tsfunctionensureRenderer(){return(renderer||(renderer=createRenderer(rendererOptions)))}这里的渲染器是一个单例,最初会被调用createRenderer创建,接下来继续深入。来到packages/runtime-core/src/renderer.ts,可以看到createRenderer会再次调用baseCreateRenderer。//packages/runtime-core/src/renderer.tsexportfunctioncreateRenderer(options:RendererOptions){returnbaseCreateRenderer(options)}baseCreateRenderer的实现有2000行,我们只需要关注几个关键点。它的返回值也是ensureRenderer()的返回值//packages/runtime-core/src/renderer.tsfunctionbaseCreateRenderer(options:RendererOptions,createHydrationFns?:typeofcreateHydrationFunctions):any{//这里省略2000行return{render,hydrate,createApp:createAppAPI(render,hydrate)}}接下来,回到起始位置。执行ensureRenderer()之后,它会接着执行createApp。这个createApp就是上一步返回的createApp,再看看做了什么工作。//packages/runtime-dom/src/index.tsexportconstcreateApp=((...args)=>{constapp=ensureRenderer().createApp(...args)//...returnapp})asCreateAppFunctionensureRenderer返回的createApp是通过createAppAPI实现的,接下来我们看看createAppAPI是如何实现的。//packages/runtime-core/src/apiCreateApp.tsexportfunctioncreateAppAPI(render:RootRenderFunction,hydrate?:RootHydrateFunction):CreateAppFunction{returnfunctioncreateApp(rootComponent,rootProps=null){constapp:App=(context.app={_uid:uid++,_component:rootComponentasConcreteComponent,_props:rootProps,_container:null,_context:context,_instance:null,version,getconfig(){},setconfig(v){},使用(插件:插件,...选项:任何[]){},mixin(mixin:ComponentOptions){},组件(名称:字符串,组件?:组件):任何{},指令(名称:字符串,指令?:Directive){},mount(rootContainer:HostElement,isHydrate?:boolean,isSVG?:boolean):any{},unmount(){},provide(key,value){}})returnapp}}可以看到createAppAPI会返回一个createApp函数,也就是我们调用createApp,当执行createApp时,将返回应用程序实例。app实例还会有use、mixin、component、directive等方法,可以给全局app添加一些扩展。案例如下,传入的第一个参数是上一步的rootComponent,也就是根组件。//index.htmlconstapp=Vue.createApp({}).use(xxx).component(xxx).mount(xxx)mountcreateApp创建应用实例后,需要调用mount渲染到页面上。接下来我们再看看mount做了什么。仍在createAppAPI中//packages/runtime-core/src/apiCreateApp.tsmount(rootContainer:HostElement,isHydrate?:boolean,isSVG?:boolean):any{if(!isMounted){constvnode=createVNode(rootComponentasConcreteComponent,rootProps)//...if(isHydrate&&hydrate){hydrate(vnodeasVNode,rootContainerasany)}else{render(vnode,rootContainer,isSVG)}isMounted=trueapp._container=rootContainer//...returnvnode.component!.proxy}elseif(__DEV__){//开发环境警告app不能重复挂载}}最终会执行render(vnode,rootContainer,isSVG)这行代码,然后查看调用createAppAPI时传入的renderer。回到baseCreateRenderer,返回时可以看到调用createAppAPI传入的renderer。//packages/runtime-core/src/renderer.tsfunctionbaseCreateRenderer(options:RendererOptions,createHydrationFns?:typeofcreateHydrationFunctions):any{//此处省略2000行return{render,hydrate,createApp:createAppAPI(render,hydrate)}}其中渲染器在baseCreateRenderer中定义常量渲染: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的例子中,第一次渲染时执行vnode它是由rootComponent创建的,rootComponent是createApp时传入的对象。container为app容器,即id为app的div,container._vnode未定义。所以它最终会出现在patch中。patch的逻辑也位于baseCreateRenderer中。代码太长,所以这里是思路。在patch中会判断vnode的type或者shapeFlag来执行相应的操作。因为vnode在第一个patch中是组件,所以会进入ShapeFlags.COMPONENT的判断,执行processComponent对组件进行处理。然后会触发mountComponent挂载组件,会触发setupComponent(instance)初始化组件的props、slots、setup等,准备proxy需要代理的数据,将template编译成render。//packages/runtime-core/src/renderer.tsconstmountComponent:MountComponentFn=(initialVNode,container,anchor,parentComponent,parentSuspense,isSVG,optimized)=>{//2.xcompat可能会在之前预先创建组件实例实际上//安装constcompatMountInstance=__COMPAT__&&initialVNode.isCompatRoot&&initialVNode.componentconstinstance:ComponentInternalInstance=compatMountInstance||(initialVNode.component=createComponentInstance(initialVNode,parentforComponent,parentSuspenseandups))//设置//ps上下文if(!(__COMPAT__&&compatMountInstance)){//...setupComponent(instance)//...}//...setupRenderEffect(instance,initialVNode,container,anchor,parentSuspense,isSVG,optimized)//...}接下来,setupRenderEffect将被执行。此方法将渲染功能封装为副作用。当依赖的响应数据发生变化时,会自动重新执行。关注componentUpdateFn。代码太长。在这里简单说一下。第一次instance.isMounted是undefined,会进入创建流程,会执行constsubTree=(instance.subTree=renderComponentRoot(instance))创建Subtree,然后通过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.htmlapp创建后,会调用mount进行挂载,mount的实现在createAppAPI内部。执行mount时会调用render函数,在baseCreateRenderer中传入render。render启动patch进行渲染,patch会递归渲染子节点。以上就是Vue3的createApp和mount的大致流程。至于为什么第一步需要经过ensureRenderer和baseCreateRenderer?baseCreateRenderer主要是平台无关的逻辑处理,存放在runtime-core中。打补丁时需要对dom进行操作时,会调用外部传入的方法进行操作,更容易实现跨端。ensureRenderer存放在runtime-dom中,主要为baseCreateRenderer提供一系列dom操作函数。如果我们要自定义渲染器,只需要实现ensureRenderer即可。而不是像Vue2那样需要fork,大大提高了Vue3的应用范围。好了,这篇文章就到此为止吧。如有错误,希望大家在评论区指出,谢谢!下一篇文章将分析Vue3的响应式原理。如果你感兴趣,别忘了关注我。让我们一起学习,共同进步。