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
相关推荐
庸俗今天不摸鱼21 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下28 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox39 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞41 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行42 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581043 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周1 小时前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei2 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring