Vue3源码阅读:createApp的过程(三)

454 阅读3分钟

vue版本:3.2.33

const { createApp } = Vue;

createApp({
  data() {
    return {
      msg: 'hello vue'
    }
  },
}).mount('#app')

今天我们来聊聊上面这段代码中的 createApp 都干了啥呢?

首先我们通过上一章节:如何调试源码?发现他进入到了 runtime-dom 目录下面的 index.ts 中的 createApp 这个方法中。代码片段如下:

这里插一句:还提供了 createSSRApp 方法,主要是提供给SSR渲染用的

export const createApp = ((...args) => {
  // 创建应用app
  const app = ensureRenderer().createApp(...args)

  // 先把刚才创建的app应用中的mount方法取出来,然后重写app.mount方法的时候会用得到
  const { mount } = app

  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // 规范化容器处理
    const container = normalizeContainer(containerOrSelector)
    if (!container) return

    // 取出根节点
    const component = app._component
    // 根节点不是函数并且没有render并且没有template,就把刚才规范化容器的innerHTML塞给根节点的template
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML
    }

    // app mount之前把容器的innerHTML清空,保持干净整洁
    container.innerHTML = ''

    // 然后把刚才取出来的mount捡起来继续执行以下, 就是createAppAPI里面的那个mount
    const proxy = mount(container, false, container instanceof SVGElement)
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

通过上面代码发现createApp通过ensureRenderer().createApp(...args)创建一个app,然后重写app.mount方法,最终把这个app对象返回出去。

那我们来看看ensureRenderer()干了啥?

const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)

function ensureRenderer() {
  return (
    renderer ||
    (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  )
}

export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}

其中rendererOptions由 下面两部分合并组成。

  • patchProp: 处理 props、Attribute、class、style、event事件这几个东西
  • nodeOps: 处理 DOM 节点操作

nodeOps对象包含:insert、remove、createElement、createText、createComment、setText、setElementText、parentNode、nextSibling、querySelector、setScopeId、cloneNode、insertStaticContent。

然后调用了 createRenderer 这个方法,并且把合并后的rendererOptions当作参数传递进去。

createRenderer 这个方法又调用了 baseCreateRenderer 的方法,

baseCreateRenderer这个方法内容就比较多了,大概有接近2000行吧。如下:

function baseCreateRenderer(options,createHydrationFns) {
  // 核心diff过程
  const patch = () => {}
  // 渲染挂载流程
  const render = (vnode, container, isSVG) => {}
  return {
    render,
    hydrate,  // 服务端渲染用的
    createApp: createAppAPI(render, hydrate)
  }
}

从代码代码可以看出baseCreateRenderer最终返回了renderhydratecreateApp

后面我们针对baseCreateRenderer中那近2000行代码到时候单独来好好聊聊,大家点个关注不迷路,好找回家路

我们通过上面代码看到createApp是通过createAppAPI(render, hydrate)这个方法实现的。

createAppAPI代码如下:

export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  // 这里的createApp才是真的创建app,里面的一些方法都是我们比较眼熟的吧
  return function createApp(rootComponent, rootProps = null) {
    if (!isFunction(rootComponent)) {
      rootComponent = { ...rootComponent }
    }

    // 创建APP默认配置
    const context = createAppContext()
    const installedPlugins = new Set()

    let isMounted = false

    // 然后这里根据createAppContext()创建的app默认配置一顿各种加工,最后把加工好的APP返回出去
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null,
      _context: context,
      _instance: null,

      version,
      get config() {
        return context.config
      },
      set config(v) {},
      use(plugin: Plugin, ...options: any[]) {},
      mixin(mixin: ComponentOptions) {},
      component(name: string, component?: Component): any {},
      directive(name: string, directive?: Directive) {},
      mount(){},
      unmount() {},
      provide(key, value) {
        return app
      }
    })

    return app
  }
}

通过上面代码,我们发现createAppAPI主要做以下几件事情:

  • 1、通过createAppContext()创建的app默认配置
  • 2、给刚才创建的app一顿各种加工,最后把加工好的APP返回出去

最后createApp流程是这样的:

createApp > ensureRenderer > createRenderer > baseCreateRenderer > createAppApi > mount

  1. 创建web端渲染器
  2. 调用 baseCreateRenderer 这个方法,就是那个2000多行代码的方法。核心diff过程
  3. 调用 createAppApi 这个方法
    • 通过createAppContext()创建的app默认配置
    • 给刚才创建的app一顿各种加工,最后把加工好的APP返回出去
  4. 然后在最开始的createApp方法里面重写 mount 方法
  5. 最终执行挂载操作。

后面我们接着来聊聊 mount 的过程。