最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 带你看Vue3源码: Vue.createApp究竟做了什么|8月更文挑战

    正文概述 掘金(无量空处)   2021-08-02   484

    笔者在上一篇文章《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"
        }
      ]
    }
    
    

    带你看Vue3源码: Vue.createApp究竟做了什么|8月更文挑战

    点击调试按钮,程序在 debugger 处暂停,然后执行到 Vue.createApp 处单步进入,断点进入到 packages/runtime-dom/src/index.ts 中的 createApp 方法。

    createApp

    我们直接来看 createApp 方法的源码,这里有部分代码删减,主要是针对 dev 环境的一些方法实现,不影响主体流程,下同。

    // packages/runtime-dom/src/index.ts
    export 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 的值如下所示:

    带你看Vue3源码: Vue.createApp究竟做了什么|8月更文挑战

    那么我们合理猜测,ensureRenderer().createApp(...args) 这行代码使用传进来的参数进行属性和方法初始化,并且挂载在 app 变量中返回

    返回 app 变量后,取出原本的 mount 方法,然后将一个新的方法实现挂载到 appmount 属性中,也就是 html 文件中 .mount('#demo') 代码块的具体实现,最后返回 app 变量。

    ensureRenderer().createApp(...args)

    单步进入到 const app = ensureRenderer().createApp(...args) 这行代码如下所示:

    带你看Vue3源码: Vue.createApp究竟做了什么|8月更文挑战

    这里涉及到两个方法:ensureRenderercreateApp,我们逐个来看。

    ensureRenderer

    // packages/runtime-dom/src/index.ts
    function ensureRenderer() {
      return (
        renderer ||
        (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
      )
    }
    

    这个方法的核心是 createRenderer,这里不展开这个方法的具体源码,我们看这个方法返回值的定义:

    // packages/runtime-core/src/renderer.ts
    export interface Renderer<HostElement = RendererElement> {
      render: RootRenderFunction<HostElement>
      createApp: CreateAppFunction<HostElement>
    }
    

    createRenderer 返回一个对象,具有 rendercreateApp 两个属性,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.ts
    mount(
      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
      }
    }
    

    带你看Vue3源码: Vue.createApp究竟做了什么|8月更文挑战

    可以看到,当我们单步跳过 render(vnode, rootContainer, isSvg) 这行代码时,hello world 字符串显示在浏览器上了,也就是说,mount 方法将 Vue 组件挂载到浏览器上,而 render 则是关键的渲染方法。

    这里有一个亮点是 mount 调用 createVNode 方法来创建虚拟节点,render 接收虚拟节点参数进行渲染,本文不再展开讲这一部分,有兴趣可以关注笔者后面的文章。

    总结

    本文为笔者连载 Vue3 源码阅读的第一篇,不涉及各种原理的解读,主要目的是在不需要阅读详细源码的前提下,快速了解 Vue3 组件的执行流程。

    希望读者在通读本文之后,能够通过调试的方式自行高效地阅读 Vue3 源码,如果各位觉得本文能给大家带来帮助,点个赞再走呗~~~


    起源地下载网 » 带你看Vue3源码: Vue.createApp究竟做了什么|8月更文挑战

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元