第二节:一文带你全面理解 vue3源码中createApp 初始应用的逻辑

前言:

在源码分析概述中, 我们对源码源码结构进行了分析, 整个vue三大核心系统, 即响应式系统, 渲染系统,编译系统. 而响应式系统渲染系统 又共同构建了vue官网上所谓的运行时构建

vue3 源码中的三大系统有明确的分界, 但在使用时, 通常并不会单独区分使用某一个系统, vue3的应用是在三个系统相互作用的结果, 至少是运行时, 即包括渲染系统响应式系统. 所以我的对vue3源码分析会根据需要交替分析三大系统中对应的源码

vue3中, 我们需要使用createApp API 来创建应用实例, 然后通过创建的实例调用mount方法将应用挂载到DOM 节点上, 因此createApp可以理解为整个vue3 应用的入口, 包括官网API都将createApp排列在第一位

所以今天这篇文章, 主要针对createApp创建vue3应用的API 进行源码分析.

在分析之前, 我先放一张createApp初始化应用的流程图: createApp初始化流程图如下:

如果你此时看到该流程图是一个呆萌的状态, 可以在看完该文章后在回头来看一下该流程图, 你就会非常清楚的知道createApp 函数都做了什么

好, 接下来我们开始正式分析createApp创建应用API的具体实现, 那接下请大家跟着我一起探寻一下createApp API 源码内部逻辑, 看看源码中都做了些什么.

1. 初始化应用

我们使用一个具体的实例作为切入点, 通过断点调试的方式对createApp源码进行分析.理解vue3初始化的过程.

实例如下:

ts 复制代码
const { createApp } = Vue

const app = createApp({
  template: '<h1>hello</h1>'
})

app.mount('#app')

这是一个最简单的实例, 在实例中, 我们首先通过解构的方式获取createApp方法. 通过createApp方法创建应用, 并通过mount方法将应用挂载到DOM

接下来我们将通过这个实例分析createApp创建vue3应用的API , 以及mount挂载方法.

2. 创建vue3应用的源码分析

2.1 定位 createApp 方法

我们知道vue3 源码采用pnpm多包管理, 目前我们只知道createAppAPI 是从vue中结构出来的, 那么该API具体来自于哪个包中, 现在并不知道.

因此在分析createAppAPI 前, 我们需要先确认该API 来自于哪个包.

我们可以通过断点调试的分时来分析, 使用单步调试, 进入到createApp方法内

此时就可以看到createApp方法来自于packages/runtime-dom包中

runtime是运行时.其中包括runtime-dom,runtime-core两个包

  • runtime-dom 处理与浏览器相关的dom 操作的API
  • runtime-core是运行时核心代码,与渲染平台无关

也就意味着runtime-dom的运行依赖于runtime-core包, 所以在runtime-dom/src/index.ts模块中一定会引入runtime-core

ts 复制代码
import { /.../ } from '@vue/runtime-core'


// 创建 vue 应用API
export const createApp = ((...args) => {
  // ...
}) as CreateAppFunction<Element>

2.2 createApp 函数分析

在确定createApp 函数后, 接下来就是分析该方法的实现 源码:

ts 复制代码
export const createApp = ((...args) => {
  // 1. 首先创建应用
  const app = ensureRenderer().createApp(...args)

  // 2. 重写mount 方法
  const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // ...后面分析
  }

  //3.返回应用
  return app
}) as CreateAppFunction<Element>

这里我对源码进行了精简, 这样非常方便的可以看出.createApp函数的核心逻辑就是创建Vue3应用对象app并返回, 至于重写mount方法, 稍后我会带大家具体分析

我们先看下createApp的入参, 用过createAppAPI的朋友, 相信大家都知道, createApp接收两个入参

  1. 根组件: 根组件可以是单文件组件, 也可以是组件的选项对象
  2. props: 向根组件传入的props数据, 该参数为可选参数.

在源码中可以看出, createApp函数通过...args剩余运算符收集所有入参, 将参数组合成为数组. 如果传入了第二个参数props, args数组收集到的数据将会有两项, 否则将只有一项, 即根组件对象.

所以createApp函数会接收根组件作为参数, 并且返回vue应用, 即方法内app就是createApp创建的应用对象

我相信大家已经看出来了, createApp函数中创建的应用对象app其本质是通过ensureRenderer().createApp(...args)创建的

通过这个结构不难看出: ensureRenderer()方法调用完毕后返回一个包含createApp方法的对象, 并通过该方法的调用创建vue应用

2.3 ensureRenderer 确认渲染器

接下来我们需要先确认ensureRenderer确认渲染器函数 源码:

ts 复制代码
// Renderer 为Web前端渲染器 , HydrationRenderer为SSR服务器端渲染使用,
let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer

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

通过ensureRenderer 函数内部逻辑不难看出,ensureRenderer函数的作用就是用来确定渲染器对象(renderer) 存在.

如果渲染器renderer不存在, 就会调用createRenderer函数创建渲染器, 根据前面的分析, 渲染器将是一个包含createApp方法的对象

初始创建vue应用renderer值为必然为空, 会调用createRenderer函数, 并传入参数rendererOptions 这里rendererOptions参数是渲染器配置对象, 主要作用就是用来操作DOM的一些方法

源码:

ts 复制代码
// 渲染器配置对象
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)


// extend 就是合并对象的方法(shared/src/general.ts)
export const extend = Object.assign

// dom 操作(runtime-core/src/nodeOps.ts	)
const doc = (typeof document !== 'undefined' ? document : null) as Document
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
  // 插入节点
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
  },
  // 创建文本节点
  createText: text => doc.createTextNode(text),
	// 创建注释节点
  createComment: text => doc.createComment(text),
	//...
}

这里将几个模块中的中的方法放在了一起, 很方便的看出, rendererOptions渲染器配置对象就是封装了DOM操作方法的对象, 由两部分组成:

  1. patchProp:处理元素的 propsAttributeclassstyleevent事件等。
  2. nodeOps:处理DOM节点,这个对象里面包含了各种封装原生DOM操作的方法。

rendererOptions对象最终会传递到渲染器里面,里面的各种方法最终会在页面渲染的过程中被调用。

至此我们暂时了解如下几件事:

  1. createApp方法的作用就是创建Vue3应用.
  2. createApp方法内真正创建Vue3应用的是渲染器对象的createApp方法
  3. createRenderer方法的作用是用来创建渲染器对象, 接收rendererOptions渲染器配置对象
  4. rendererOptions配置对象中包含DOM操作的方法

因此要创建Vue3 应用必须先创建渲染器对象, 因此接下来我们继续分析createRenderer方法的实现

2.4 createRenderer 创建渲染器方法

createRenderer方法来源于runtime-core/src/render.ts模块 具体源码实现如下

ts 复制代码
// 创建渲染器方法
function createRenderer(options) {
  return baseCreateRenderer(options)
}

// 真正创建渲染器函数
function baseCreateRenderer(options,createHydrationFns) {
  // ... 省略渲染方法
  
	// 渲染函数
  const render = (vnode, container, isSVG) => {
     //...
  }
  let hydrated;
  let hydrateNode;
  if (createHydrationFns) {
    ;[hydrate, hydrateNode] = createHydrationFns(internals)
  }
  
  return {
    render,
    hydrate,  
    createApp: createAppAPI(render, hydrate)
  }
}

通过源码可以看出createRenderer函数返回baseCreateRenderer方法执行的结果, 即真正创建渲染器对象的函数是baseCreateRenderer baseCreateRenderer创建的渲染器对象包括render, hydrate, createApp三个方法

获取到渲染器对象后, 就会通过渲染器对象调用createApp方法创建应用. 而createApp方法的值是通过createAppAPI方法创建的, 同时会将render, hydrate,两个方法作为参数传入createAppAPI方法中.

所以接下来我们需要看一下createAppAPI方法的实现

2.5 createApp API 方法

createAppAPI方法来自于runtime-core/src/apiCreateApp.ts

具体实现如下

ts 复制代码
let uid = 0

export function createAppAPI(render,hydrate){
  
  // 返回创建应用的createApp 方法(利用闭包缓存render, hydrate 参数)
  return function createApp(rootComponent, rootProps = null) {
    // rootComponent 就是传入的根组件
  	// rootProps 为向根组件传入props 数据

    // 1. 如果根组件不是函数, 则进行一次浅拷贝
    if (!isFunction(rootComponent)) {
      rootComponent = extend({}, rootComponent)
    }

    // 2.rootProps 为传入根组件的props , 参数必须是一个对象或null
    if (rootProps != null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }

    // 3. 创建vue应用实例上下文对象, 就是一些默认配置
    const context = createAppContext()

  	// 4. 初始化插件集合, 记录通过use 创建的插件
    const installedPlugins = new WeakSet()

    // 5. 始化应用挂载状态,默认为false
    let isMounted = false

    // 6. 创建 vue 应用实例对象
		// 同时将应用实例存储到context上下文的app属性
    const app: App = (context.app = {
      // 初始化应用属性
      _uid: uid++,  // 项目中可能存在多个vue实例,需使用id标识
      _component: rootComponent as ConcreteComponent,  // 根组件
      _props: rootProps,  // 传递给根组件的props
      _container: null,  // 挂载点: DOM容器
      _context: context,  // app上下文
      _instance: null,   // 应用实例

      version,   // 版本

      // 定义了一个访问器属性app.config,只能读取,不能直接替换
      get config() {return context.config},

      set config(v) {},

      // 挂载插件, 返回app对象
      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 },

      runWithContext(fn) {/*... 省略代码*/ }
    })

    // __COMPAT__ 判断是否兼容vue2
    // 若开启兼容,安装vue2相关的API
    if (__COMPAT__) {
      installAppCompatProperties(app, context, render)
    }

    // 返回 app 对象
    return app
  }
}

源码中createAppAPI函数返回了一个函数, 返回的这个函数才是真正的createApp函数.

因此该返回的函数就是最后用来创建应用对象的createApp方法

分析至此,我们就可以知道了

  1. createAppAPI 创建应用实例对象并返回
  2. createAppAPI 中创建应用对象, 是通过调用渲染器{render,hydrate,createApp}中的createApp方法创建的
  3. 渲染器中的createApp方法则是createAppAPI函数的返回函数

createApp创建的应用对象就是包含了一些属性和常用的use,mixins,component,directive,mount,unmount,provide方法的对象. 因此可以通过app应用对象调用mount挂载应用

3. mount 挂载源码分析

在上面我们已经分析了createApp函数, 在调用完毕后返回vue应用实例对象, 在使用时会通过应用实例对象调用mount方法挂载应用.

3.1 确定mount 方法

通过前面的分析,我们已经明白, 创建应用实例的createApp函数是通过调用createApp API方法返回的createApp 创建的应用实例. 在此方法中可以看到生成应用对象app具有mount方法

源码:

ts 复制代码
export function createAppAPI(render,hydrate){
  // 创建vue应用实例的方法
  return function createApp(rootComponent, rootProps = null) {
    //...

    // 应用实例对象
    const app: App = (context.app = {
      //..
      // 应用实例对象挂载mount 方法
      mount(rootContainer,isHydrate,isSVG) {
         // ...
      },
      //...
    })

   //..

    // 返回应用实例对象
    return app
  }
}

但我们调用app.mount()挂载应用时并不是调用该mount 方法, 原因在于createApp方法中对当前mount方法进行了重写

ts 复制代码
export const createApp = ((...args) => {
  // 1. 首先创建应用
  const app = ensureRenderer().createApp(...args)

  // 2. 重写mount 方法
  const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // ...后面分析
  }

  //3.返回应用
  return app
}) as CreateAppFunction<Element>

源码很清楚的表明, createApp函数中创建完应用实例对象, 接着通过解构的方式将应用实例对象appmount方法记录在变量mount

然后重写app对象的mount方法进行重写, 赋值了一个新的方法

也就是在在我们通过app.mount()挂载应用时, 进入的是createApp函数中对app应用对象重写的mount方法

3.2 mount 挂载

在实例中,我们调用mount方法进行挂载时,传入了一个字符串#app , 以此获取到挂载点DOM元素

ts 复制代码
app.mount('#app')

mount具体是如何挂载的呢?, 我们首先分析一下createApp函数中重写的mount方法

重写的mount方法

ts 复制代码
export const createApp = ((...args) => {
  // 创建应用实例对象
  const app = ensureRenderer().createApp(...args)

  // 重写mount 方法
  // 备份mount 方法
  const { mount } = app

  // 重写mount 方法
  app.mount = (containerOrSelector: Element | ShadowRoot | string) => {
    // 1. 获取挂载容器(dom元素)
    const container = normalizeContainer(containerOrSelector)
    // 没有容器直接return 无法挂载应用
    if (!container) return

    // 2. 处理根组件
    // 通过应用对象获取根组件, 即createApp() 方法第一个参数
    const component = app._component

    // 验证根组件是一个对象,且不具有render, template属性, 则使用挂载点内容作为模板 
    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.
      
      // 需要确保挂载点模板是可信的, 因为模板中可能存在JS表达式
      component.template = container.innerHTML
      
    }

    // 清空挂载点
    // clear content before mounting
    container.innerHTML = ''

    // 3. 调用 mount 真正的挂载
    // 调用从应用实例上备份的mount 进行挂载
    const proxy = mount(container, false, container instanceof SVGElement)

    // 挂载完成后, 清理v-clock指令, 为容器添加data-v-app 标识
    if (container instanceof Element) {
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }

    // 返回根组件实例对象(注意不是app应用实例对象)
    return proxy
  }

  // 返回应用对象
  return app
}) as CreateAppFunction<Element>

可以看到重写的mount方法通过调用normalizeContainer获取挂载点容器, 其具体实现如下:

源码:

ts 复制代码
// 获取挂载点dom 容器
function normalizeContainer(
  container: Element | ShadowRoot | string
): Element | null {

  // 1. 参数为字符串, 则通过querySelector 获取dom 元素
  if (isString(container)) {
    const res = document.querySelector(container)
    return res
  }

  // 2. 参数不是字符串, 则直接返回
  return container as any
}

在调用mount方法挂载时, 参数可以是字符串, 也可以是真实的DOM元素.因此在该方法中针对参数进行判断,

  • 如果参数是字符串, 则认为是选择器, 通过querySelector获取DOM元素
  • 如果参数不是字符串, 则认为参数是真实的DOM 元素, 直接返回

上面重写的mount方法, 其实最主要的就是做了三件事

  1. 获取挂载点容器, 通过normalizeContainer方法获取
  2. 处理根组件对象, 根组件对象如果不具有渲染函数render方法和模板template, 则使用挂载点元素内容作为template模板
  3. 调用应用对象app原本的mount方法挂载根组件, 挂载完成后为容器添加特定的属性

3.3 应用对象本身的mount 方法

在重写mount方法中最终会调用应用对象app本身的mount方法进行正式的挂载,

mount方法接收三个参数

  1. 挂载点容器: container
  2. 是否为SSR: 传入固定值false
  3. 是否为SVG元素

mount方法具体实现如下

ts 复制代码
let isMounted = false

const app = {
  //...

  // app 挂载方法
  mount(rootContainer: HostElement,isHydrate?: boolean,isSVG?: boolean) {
    // 判断是否已经挂载 
    if (!isMounted) {
      // #5571

      // 如果挂载容器具有__vue_app__, 表示当前容器已就作为其他应用挂载容器
      if (__DEV__ && (rootContainer as any).__vue_app__) {
        warn(
          `There is already an app instance mounted on the host container.\n` +
            ` If you want to mount another app on the same host container,` +
            ` you need to unmount the previous app by calling \`app.unmount()\` first.`
        )
      }

      // 1. 调用createVNode 创建根组件的VNode
      // rootComponent, rootProps 是createApp 调用时传入的参数, 根组件对象与props
      const vnode = createVNode(rootComponent, rootProps)
      
      // store app context on the root VNode.
      // this will be set on the root instance on initial mount.
      // 在根VNode上绑定应用上下文对象,在挂载完毕后绑定到根组件实例对象上
      vnode.appContext = context

      // HMR root reload
      // 热更新
      if (__DEV__) {
        context.reload = () => {
          render(cloneVNode(vnode), rootContainer, isSVG)
        }
      }

      // 2. 渲染 VNode
      // 其他渲染方式
      if (isHydrate && hydrate) {
        hydrate(vnode as VNode<Node, Element>, rootContainer as any)
      } else {
        // 浏览器调用render 渲染根VNode 
        // render 函数在创建创建渲染函数中定义, 传递到createAppAPI, 通过闭包缓存
        render(vnode, rootContainer, isSVG)
      }

      // 挂载完毕后, isMounted 状态设置为true
      isMounted = true

      // 应用实例上绑定挂载容器
      app._container = rootContainer
        
      // for devtools and telemetry
      // 将app 应用实例对象绑定到容器上, 因此可以通过容器访问app 实例
      ;(rootContainer as any).__vue_app__ = app

      // 开发环境调试
      if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
        app._instance = vnode.component
        devtoolsInitApp(app, version)
      }

      
      return getExposeProxy(vnode.component!) || vnode.component!.proxy
    } else if (__DEV__) {
      warn(
        `App has already been mounted.\n` +
          `If you want to remount the same app, move your app creation logic ` +
          `into a factory function and create fresh app instances for each ` +
          `mount - e.g. \`const createMyApp = () => createApp(App)\``
      )
    }
  },
}

通过上面的分析, 在挂载应用实例时, 去除边缘判断,核心逻辑就以下两点

  1. 通过调用createVNode创建虚拟节点, 参数为根组件对象和向根组件传入的props 对象
  2. 通过调用rendder方法渲染虚拟节点

接下来我们分别分析创建虚拟节点和渲染虚拟节点逻辑

3.4 createVNode 创建VNode 函数

虚拟节点的本质就是一个JS对象, 通过属性描述一个DOM 节点, 包括tag, props,children

具体看一下createVNode函数的实现

ts 复制代码
// 创建createVNode 函数
export const createVNode = ( __DEV__ ? createVNodeWithArgsTransform : _createVNode )

// 创建具有参数转换的VNode 函数
const createVNodeWithArgsTransform = (...args) => {
  
  // 调用_createVNode 创建VNode
  return _createVNode(
    ...(vnodeArgsTransformer
      ? vnodeArgsTransformer(args, currentRenderingInstance)
      : args)
  )
}

通过代码不难看出, 真正来创建虚拟节点的函数是_createVNode. 开发环境调用createVNodeWithArgsTransform也只不过根据vnodeArgsTransformer函数是否存在来转换一下参数, 最终函数返回_createVNode调用的结果

因此我们将通过_createVNode函数分析创建的虚拟节点(vnode) _createVNode的具体实现

ts 复制代码
// _createVNode 第一个参数是必传的, 后面参数都具有默认值
function _createVNode(
  type,    // 创建VNode的类型
  props = null,
  children = null,
  patchFlag = 0,
  dynamicProps = null,
  isBlockNode = false
) {
  // type 不存在  或 type 值为 Symbol(v-ndc)
  // 则提示type 无效, 并将type 认定为 Comment 注释类型
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    if (__DEV__ && !type) {
      warn(`Invalid vnode type when creating vnode: ${type}.`)
    }
    type = Comment
  }

  // 判断type 是否已经是一个VNode 
  if (isVNode(type)) {
   //...
  }

  // 调用isClassComponent 判断是否为有状态的组件
  // class component normalization.
  if (isClassComponent(type)) {
    type = type.__vccOpts
  }

  // 2.x async/functional component compat
  if (__COMPAT__) {
    type = convertLegacyComponent(type, currentRenderingInstance)
  }

  // props 参数存在, 则规范处理class, style
  // class & style normalization.
  if (props) {
   //...
  }

  // 判断VNode 标识, 采用二进制表示, 示例代码标识为 100 = 4
  // encode the vnode type information into a bitmap
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
      ? ShapeFlags.SUSPENSE
      : isTeleport(type)
        ? ShapeFlags.TELEPORT
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT
            : 0

  // 判断节点状态
  if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
    //...
  }

  // 返回createBaseVNode 创建的VNode
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

从源码中可以看出, _createVNode函数只是对需要创建虚拟节点的参数进行各种判断检查, 规范props数据, 以及创建VNode 参数的节点标识 最后返回createBaseVNode函数创建的节点

createBaseVNode函数的实现如下:

ts 复制代码
// 创建VNode
function createBaseVNode(
  type,
  props = null,
  children = null,
  patchFlag = 0,
  dynamicProps = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
  // 虚拟节点对象 VNode
  const vnode = {
    __v_isVNode: true,
    __v_skip: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    slotScopeIds: null,
    children,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null,
    ctx: currentRenderingInstance
  } as VNode

  // ...

  return vnode
}

可以看出createBaseVNode函数最大的作用就是创建VNode, 并返回该虚拟节点, 即JavaScript 对象

3.5 render 渲染VNode

在创建完虚拟DOM 后, 就会调用render方法渲染虚拟节点 render函数是在讲createRenderer的时候出现的,是在baseCreateRenderer中定义的

具体源码如下

ts 复制代码
function baseCreateRenderer(options,createHydrationFns) {
	//...
  // render 渲染VNode 函数
  const render = (vnode, container, isSVG) => {
    // 如果VNode 不存在, 有可能之前已经挂载, 那么将会执行卸载操作
    if (vnode == null) {
      // container._vnode 表示上一次渲染的VNode, 存在则会执行卸载操作
      if (container._vnode) {
        unmount(container._vnode, null, null, true  // 卸载VNode
      }
    } else {
      // 如果VNode 存在, 则调用patch 方法, 判断处理除此渲染或更新渲染
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    

    // 刷新任务队列, 暂不分析
    flushPreFlushCbs()
    flushPostFlushCbs()

    // 渲染完毕后, 容器记录渲染的VNode
    container._vnode = vnode
  }

  //...
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

render 渲染函数的主要作用就是分发

  1. 渲染的vnode为空, 则判断之前已渲染的情况下,则调用unmount进行卸载操作
  2. 渲染的vnode不为空, 则调用patch函数, 根据情况执行初始渲染或更新渲染操作

4. 初始化应用总结

分析到这里,以上的内容就是createApp源码的基本内容。主要作用就是确定渲染器,创建一个Vue应用实例,最后进行挂载,挂载之后具体渲染逻辑,后面分析

接下来对本节初始化应用进行总结,主要有两部分内容: 创建应用实例对象挂载应用

4.1 创建应用实例对象

创建应用实例对象步骤:

  1. 调用createApp函数创建应用实例
  2. createApp函数中通过createRenderer函数创建渲染器
  3. createRenderer函数返回渲染器对象(renderer): {render, createApp }
  4. 调用渲染器中的createApp方法创建应用实例对象
  5. createApp函数中重写mount挂载方法,并返回应用实例对象

4.2 挂载应用

调用应用实例对象的mount方法进行挂载

  1. 首先在重写的mount方法中处理获取挂载容器的DOM元素, 以及渲染根组件的模板
  2. 其后调用应用实例对象本身的mount方法
  3. mount挂载方法中根据根组件调用createVNode函数创建vnode
  4. 之后在调用渲染器中的render渲染函数, 渲染根组件生成的vnode

简单来说初始化应用就是做了如下几件事

  1. 创建Vue应用实例对象。
  2. 确定应用挂载容器节点。
  3. 创建根组件vnode对象
  4. 执行render渲染根组件vnode

至此, 整个createApp初始化应用我就带大家分析完了, 你可以回头去前言再看一眼createApp应用的流程图.

最后: 如果觉得这篇文章对你有帮助, 点赞关注不迷路, 你的支持是我的动力!

相关推荐
熊的猫39 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
mosen8681 小时前
Uniapp去除顶部导航栏-小程序、H5、APP适用
vue.js·微信小程序·小程序·uni-app·uniapp
别拿曾经看以后~2 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人3 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人3 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香3 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel