乐趣区

关于前端:带你看Vue3源码-VuecreateApp究竟做了什么

文章首发: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.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 的值如下所示:

那么咱们正当猜想,ensureRenderer().createApp(...args) 这行代码应用传进来的参数进行属性和办法初始化,并且挂载在 app 变量中返回

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

ensureRenderer().createApp(...args)

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

这里波及到两个办法: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
  }
}

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

这里有一个亮点是 mount 调用 createVNode 办法来创立虚构节点,render 接管虚构节点参数进行渲染,本文不再开展讲这一部分,有趣味能够关注笔者前面的文章。

总结

本文为笔者连载 Vue3 源码浏览的第一篇,不波及各种原理的解读,次要目标是在不须要浏览具体源码的前提下,疾速理解 Vue3 组件的执行流程。

心愿读者在通读本文之后,可能通过调试的形式自行高效地浏览 Vue3 源码,如果各位感觉本文能给大家带来帮忙,点个赞再走呗~~~

退出移动版