Bootstrap

Vue3源码 | createApp都干了什么?

创建Vue应用实例,是一个项目的开始,这篇文章看看createApp内部都发生了什么。当然如果你对Vue3响应式系统感兴趣,也可以先看看这两篇文章:

VUE3使用

这里看下Vue3是如何进行应用初始化。

// vue3 应用初始化
import { createApp } from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')

// 组件渲染和未捕获错误配置的处理程序
app.config.errorHandler = (err, vm, info) => {}
// 添加全局属性
app.config.globalProperties.$http = () => {} // 这里相当于挂载到Vue2的 Vue.prototype
// 指定一种方法识别Vue之外定义的自定义元素
app.config.isCustomElement = tag => tag.startsWith('ion-')
// 注册组件
app.component('my-component', {})
// 检索组件
const MyComponent = app.component('my-component')
// 注册指令
app.directive('my-directive',{})
// 设置一个可以注入到应用程序内所有组件中的值。组件应使用inject来接收提供的值。
app.provide('user', 'administrator')
// 卸载应用程序
 app.unmount()
// 安装vue插件
import MyPlugin from './plugins/MyPlugin'
app.use(MyPlugin)
...

Vue3跟Vue2初始化区别不大,都是创建应用实例,然后挂载到DOM节点上。这里也可以看出,Vue是通过JS渲染页面,跟传统页面DOM直出的方式是不一样。DOM直出,简单说是,请求后返回的HTML页面是最终的呈现效果。

createApp流程

createApp

createApp用于创建应用实例,下面看看内部代码实现:

export const createApp = ((...args) => {
  // 创建app实例
  const app = ensureRenderer().createApp(...args)

  const { mount } = app
  // 重写mount方法
  app.mount = (containerOrSelector: Element | string): any => {
    // 标准化容器
    const container = normalizeContainer(containerOrSelector)
    if (!container) return
    const component = app._component
    // 组件不存在render函数和模板template,则使用container的innerHTML做为组件模板
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML
    }
    // 挂载前,清空容器的内容
    container.innerHTML = ''
    // 挂载容器
    const proxy = mount(container)
    container.removeAttribute('v-cloak')
    container.setAttribute('data-v-app', '')
    return proxy
  }

  return app
}) as CreateAppFunction

通过上面源码解析,我们可以看出createApp主要是干了两件事:

看完会存在两个主要疑问, 是干啥用的?为什么要重写 方法,而不直接使用呢?

ensureRenderer

用于创建渲染器,渲染器核心代码处于 文件。

baseCreateRenderer// 可以看出render存在2种类型的渲染器
let renderer: Renderer | HydrationRenderer

// 延迟创建渲染器,当用户只是使用reactive响应库,可以做tree-shaking优化
function ensureRenderer() {
  return renderer || (renderer = createRenderer(rendererOptions))
}

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

// HydrationRenderer渲染器,也是只在调用的时候创建,方便做tree-shaking优化
export function createHydrationRenderer(
  options: RendererOptions
) {
  return baseCreateRenderer(options, createHydrationFunctions)
}

从分析可以看出:

baseCreateRenderer

这个函数是个大家伙,1800行左右的代码,包含了组件渲染的核心逻辑。这里只抽离挂载过程相关逻辑,涉及组件更新等其他渲染逻辑,留到后面再深入研究。

function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {
   
   // 其他关于组件渲染逻辑以及hydrate相关这里省略
   
   // 渲染函数
   const render: RootRenderFunction = (vnode, container) => {
    if (vnode == null) {
      // 虚拟节点不存在,则销毁
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      // 虚拟节点存在,则更新或创建
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    // 缓存虚拟节点数据,作为已完成渲染的标识
    container._vnode = vnode
  }
  
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

可以看出 主要实现了:

createAppAPI

组件渲染核心部分后面看,这里看看创建实例的API实现。

export function createAppAPI(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction {
  // createApp接收2个参数,根组件和根组件的属性
  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
		
    // 重写上下文对象的app属性
    const app: App = (context.app = {
      _component: rootComponent as Component,
      _props: rootProps,
      _container: null,
      _context: context,

      version,
			// 暴露的应用实例的配置
      get config() {
        return context.config
      },

      set config(v) {
        if (__DEV__) {
          warn(
            `app.config cannot be replaced. Modify individual options instead.`
          )
        }
      },
			// 插件的安装,可以看出,插件如果是对象,必须有install方法;如果是函数,则默认是安装方法
      use(plugin: Plugin, ...options: any[]) {
        if (installedPlugins.has(plugin)) {
          __DEV__ && warn(`Plugin has already been applied to target app.`)
        } else if (plugin && isFunction(plugin.install)) {
          installedPlugins.add(plugin)
          plugin.install(app, ...options)
        } else if (isFunction(plugin)) {
          installedPlugins.add(plugin)
          plugin(app, ...options)
        } else if (__DEV__) {
          warn(
            `A plugin must either be a function or an object with an "install" ` +
              `function.`
          )
        }
        return app
      },

      mixin(mixin: ComponentOptions) {
        if (__FEATURE_OPTIONS_API__) {
          if (!context.mixins.includes(mixin)) {
            context.mixins.push(mixin)
          }
        }
        return app
      },

      component(name: string, component?: PublicAPIComponent): any {
        if (__DEV__) {
          validateComponentName(name, context.config)
        }
        // 组件存在,则返回
        if (!component) {
          return context.components[name]
        }
        if (__DEV__ && context.components[name]) {
          warn(`Component "${name}" has already been registered in target app.`)
        }
        // 不存在就注册
        context.components[name] = component
        return app
      },

      directive(name: string, directive?: Directive) {
        if (__DEV__) {
          validateDirectiveName(name)
        }

        if (!directive) {
          return context.directives[name] as any
        }
        if (__DEV__ && context.directives[name]) {
          warn(`Directive "${name}" has already been registered in target app.`)
        }
        context.directives[name] = directive
        return app
      },

      mount(rootContainer: HostElement, isHydrate?: boolean): any {
        if (!isMounted) {
          // 根据根组件创建虚拟节点
          const vnode = createVNode(rootComponent as Component, rootProps)
          vnode.appContext = context

          // HMR root reload
          if (__DEV__) {
            context.reload = () => {
              render(cloneVNode(vnode), rootContainer)
            }
          }
 
          render(vnode, rootContainer)
          
          isMounted = true
          app._container = rootContainer
          // for devtools and telemetry
          ;(rootContainer as any).__vue_app__ = app

          return vnode.component!.proxy
        }
      },

      unmount() {
        if (isMounted) {
          render(null, app._container)
          devtoolsUnmountApp(app)
        }
      },

      provide(key, value) {
        context.provides[key as string] = value
        return app
      }
    })

    return app
  }
}

从上面的代码,我们可以了解到createAppAPI主要实现了:

总结

至此分析了createApp大致的流程,内部更细致的实现,后续再根据内容深入分析,这里再总结下整个过程。

下一篇,我们将立足这篇文章,接着深入render函数内部,先看看vNode是什么,再瞧瞧DOM Diff过程。