createApp都发生了什么

前言

这是vue3系列源码的第一章,使用的vue3版本是3.2.45

createApp的流程

js 复制代码
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

这一段是我们在main文件中用到的。今天我们看看这个createApp到底是怎么过来的。

runtime-dom部分中,首先export出了一个createApp函数,这个函数主要的工作就是定义了一个app的对象,然后return出来。 也就是说,我们通过这个函数最终得到的是一个app的对象,那么这个对象是怎么来的,都有什么。

这个函数的第一行

js 复制代码
const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)
...

ensureRenderer()

先看看ensureRender函数

js 复制代码
function ensureRenderer() {
  return (
    renderer ||
    (renderer = createRenderer(rendererOptions))
  )
}

这个函数其实就是reutrn了由createRender函数返回的对象

那么再看看cerateRenderer函数

js 复制代码
function createRenderer(options) {
  return baseCreateRenderer(options)
}

这个函数是定义在runtime-core中的,也是直接返回了一个由baseCreateRenderer函数返回的对象, 接着看看baseCreateRenderer函数

js 复制代码
 return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }

这个函数最终返回了一个对象,包含了createApp这个属性。

所以,我们第一步ensureRenderer函数其实就是通过层层转包,最终返回一个带有createApp属性的对象。

baseCreateRenderer

那么我们再回头看看,baseCreateRenderer这个函数都干了啥。

js 复制代码
function baseCreateRenderer(options, createHydrationFns){
    const target = getGlobalThis()
    
      const {
              insert: hostInsert,
              remove: hostRemove,
              patchProp: hostPatchProp,
              createElement: hostCreateElement,
              createText: hostCreateText,
              createComment: hostCreateComment,
              setText: hostSetText,
              setElementText: hostSetElementText,
              parentNode: hostParentNode,
              nextSibling: hostNextSibling,
              setScopeId: hostSetScopeId = NOOP,
              insertStaticContent: hostInsertStaticContent
          } = options
       
       const patch = () => {...}
       const render = () => {...}
       const ...
}

首先是getGlobalThis

js 复制代码
const getGlobalThis = () => {
    return (_globalThis ||
        (_globalThis =
            typeof globalThis !== 'undefined'
                ? globalThis
                : typeof self !== 'undefined'
                    ? self
                    : typeof window !== 'undefined'
                        ? window
                        : typeof global !== 'undefined'
                            ? global
                            : {}));
}

这个函数其实就是用来获取全局对象的,我们在浏览器运行,得到的其实就是window对象。

getGlobalThis函数下面是一个对option对象的解构。

这个option对象是怎么来的呢。

是在上文中的ensureRenderer函数的闭包中出现的,传进来的值是rendererOptions

在上文是这么定义的:

js 复制代码
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)

我们看看这个renderOptions到底是个什么东西。

里面包含了一些方法,这些方法都是操作虚拟dom的。

option解构完了之后,后面又是一些方法的定义,这里我们先不用管。

那么ensoureRenderer函数差不多就这样了。

createApp

接下来,我们再看看createApp的过程。

上面说到这里的createApp其实是createAppAPI这个函数。

createAppAPI

js 复制代码
function createAppAPI(render, hydrate) {
  return function createApp(rootComponent, rootProps = null) {
  ...

大家看,又像套娃一样,createAPPAPI函数原来又是返回一个createApp函数。

这已经是createApp这个函数名第三次出现,每次出现还都不是同一个函数。

不要慌,那我们就继续深入这个createApp函数中去。

js 复制代码
return function createApp(rootComponent, 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,
      
      use(){...},
      mixin(){...},
      mount(){...}
      ...
   }
   return app
}

首先看一下createAppContext这个函数

js 复制代码
function createAppContext(): AppContext {
  return {
    app: null as any,
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: {}
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
    optionsCache: new WeakMap(),
    propsCache: new WeakMap(),
    emitsCache: new WeakMap()
  }
}

这个对象最终返回了一个vue全局上下文,里面将会包含全局的一些数据。这个对象下一步被存在了app对象的_context字段中。

那么在app对象中,这里定义了一些属性和方法,其中方法有我们常用的use``mixin``mount等。

而且这里的mount其实就是我们在main.js文件中调用的那个mount方法的核心。 关于mount部分,我们下一篇文章会讲到。

那么到了这里,我们其实就看完了runtime-core部分的createApp函数。 其实就是返回了一个将要包含各种全局数据的app对象。

app

让我们再回到runtime-dom中的createApp函数中。

js 复制代码
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) {
      // __UNSAFE__
      // Reason: potential execution of JS expressions in in-DOM template.
      // The user must make sure the in-DOM template is trusted. If it's
      // rendered by the server, the template should not contain any user data.
      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
})

这个函数的剩下部分其实就是对得到的app.mount函数进行了装饰,核心还是createAppAPI函数中定义的mount方法。

那么,到此,我们差不多就弄明白了createApp这个函数的作用,核心就是返回了一个包含了各种全局数据的app对象。

流程图

graph TB subgraph runtime-dom direction TB subgraph createAppdom[createApp] direction TB appobj(app) --as--> appdom("app = ensureRenderer().createApp(...args);") --> ensureRenderer -- return --> createRenderer end main("createApp(App)") -- return--> appobj end subgraph runtime-core direction TB createRenderer[createRenderer] -- return --> baseCreateRenderer subgraph baseCreateRenderer["baseCreateRenderer(options, createHydrationFns)"] direction TB createAppAPI("createAppAPI(render, hydrate)") -- return --> createApp("fun createApp(rootComponent, rootProps)") --return--> app subgraph app["obj app"] direction TB oteher(" use mixin component directive unmount provid mount") end end end
相关推荐
世俗ˊ16 分钟前
CSS入门笔记
前端·css·笔记
子非鱼92117 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
6230_21 分钟前
git使用“保姆级”教程1——简介及配置项设置
前端·git·学习·html·web3·学习方法·改行学it
想退休的搬砖人30 分钟前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
加勒比海涛1 小时前
HTML 揭秘:HTML 编码快速入门
前端·html
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
清汤饺子1 小时前
实践指南之网页转PDF
前端·javascript·react.js
蒟蒻的贤1 小时前
Web APIs 第二天
开发语言·前端·javascript