Vue3源码阅读笔记-Vue.createApp

537 阅读3分钟

Vue.createApp到底做了什么?

当调用Vue.createApp时实际上会来到下面这个函数

export const createApp = ((...args) => {
  // 先调用ensureRenderer函数,确保有renderer渲染器
  // 最终ensureRenderer()会返回,下面的对象
  /**
   * {
      render,
      hydrate,
      createApp: createAppAPI(render, hydrate)
    }
    调用上面的createApp实际上是调用createAPI函数的的返回值
   */
  const app = ensureRenderer().createApp(...args)

  // 解构app对象的mount方法
  const { mount } = app
  // 重写app的mount方法
  // 在app.mount方法里调用了上面的mount方法
  app.mount = () => {
      // ...
  }
  return app
})

ensureRenderer函数

// 这里延迟创建渲染器,是为了当用户没有使用到渲染器时(例如用户只使用到了响应式模块),渲染器可以被tree-shaking掉
let renderer
function ensureRenderer() {
    // 有渲染器直接返回,没有则调用createRenderer,并将createRenderer返回值保存到renderer中
  return (
    renderer ||
    (renderer = createRenderer(rendererOptions))
  )
}

createRenderer函数

export function createRenderer(options) {
    // createRenderer调用baseCreateRenderer函数
  return baseCreateRenderer(options)
}

baseCreateRenderer函数

function baseCreateRenderer(options, createHydrationFns){
    // 由于该函数实在是太长,2000多行,这里直接展示该函数的返回值和返回值用到的值
    // render函数
    const render = (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
  }
    // ...
    return {
        render,
        hydrate,
        // 这里将render函数作为参数传递进去用到了柯里化
        // 为什么要用柯里化呢,因为调用这里渲染domo的render函数会被该函数返回函数所使用,
        // 我们也可以自己实现一个render函数,传递进去,这样就可以使用我们自己的render函数,这样就可以让变得更加可定制化
        createApp: createAppAPI(render, hydrate)
  }
}

createAppAPI函数

export function createAppAPI(render, hydrate) {
  // baseCreateRenderer返回的对象中createApp属性实际是该函数
  return function createApp(rootComponent, rootProps = null) {
    
    if (rootProps != null && !isObject(rootProps)) {
        // 处理props
    }

    // 创建app的上下文,将其添加到app的_context属性中
    const context = createAppContext()
    const installedPlugins = new Set()

    // 创建一个变量来记录是否被挂载过
    let isMounted = false

    // 在这里为context添加app属性指向app对象,也就是说context和app是相互引用的
    const app = (context.app = {
      _uid: uid++,
      _component: rootComponent,
      _props: rootProps,
      _container: null,
      _context: context,
      _instance: null,
      version,
      get config() {
        return context.config
      },
      set config(v) {
      // ...
      },

      use(plugin, ...options) {
      // ...
        return app
      },

      mixin(mixin) {
      // ...
        return app
      },

      component(name, component) {
      // ...
        return app
      },

      directive(name, directive) {
      // ...
        return app
      },

      mount(rootContainer, isHydrate, isSVG) {
      // ...
      },

      unmount() {
      // ...
      },

      provide(key, value) {
      // ...
        return app
      }
    })
    return app
  }
}

总结

  • 调用Vue.createApp时,会先调用ensureRenderer函数,该函数是为了确保有渲染器。
  • 如果没有渲染器,那么ensureRenderer函数会调用createRenderer函数,创建一个渲染器。
  • createRenderer函数内部调用了baseCreateRenderer函数,该函数才是真正创建渲染器的函数。在这个函数会返回一个渲染器对象,该对象有render函数和createApp函数
  • 有了渲染器对象,就会开始调用渲染器对象上的createApp函数,这个函数就是真正创建app对象的函数。
  • 渲染器对象上的createApp函数中,创建了app对象,并给该对象添加了use,mount,mixin,component,directive,unmount,provide等方法。同时还会创建一个app上下文(context)和app对象互相引用,还创建了isMounted变量,用来记录是否被挂载过。

其他vue3源码阅读笔记

app.mount的过程

diff算法