Vue3源码阅读笔记-app.mount

1,961 阅读1分钟

app.mount('#app')的大致流程

app.mount实际调用的函数

  • 会先通过document.querySelector获取container节点。如果该节点不存在,直接返回。
  • 清空container的innerHTML
  • 调用mount函数
  • 如果container是Element的实例,删除v-cloak属性,以及添加data-v-app属性。
app.mount = (containerOrSelector) => {
    // 如果传递的是一个字符串
    // 内部会通过document.querySelector获取到container
    const container = normalizeContainer(containerOrSelector)
    // 如果container没有值,直接返回
    if (!container) return
    // component保存了我们通过Vue.createApp(App)传递的参数对象App
    const component = app._component
    // 当component不是函数,并且没有render属性,并且没有template属性,进入该if
    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
      // 2.x compat check
      if (__COMPAT__ && __DEV__) {
        for (let i = 0; i < container.attributes.length; i++) {
          const attr = container.attributes[i]
          if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
            compatUtils.warnDeprecation(
              DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
              null
            )
            break
          }
        }
      }
    }

    // 挂载之前清空container的innerHTML
    container.innerHTML = ''
    // 调用mount函数
    const proxy = mount(container, false, container instanceof SVGElement)
    // 如果container是一个元素类型
    if (container instanceof Element) {
    // 删除v-clock这个属性,这也是为什么可以使用v-cloak隐藏未编译的 Mustache 标签直到组件实例准备完毕
    // 添加data-v-app属性,这也是为什么我们的根组件会有这个属性的原因
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }
    return proxy
  }

mount函数

  • mount函数会根据isMounted这个变量判断是否已经被挂载过。
  • 没有被挂载过:调用createVnode创建vnode。调用render函数将vnode和container作为参数传递进去
  • 如果被挂载过并且是在开发环境中,发出警告。
function mount(rootContainer, isHydrate, isSVG) {
        // 如果没有被挂载过
        if (!isMounted) {
          // 创建vnode
          const vnode = createVNode( rootComponent, rootProps)
          // store app context on the root VNode.
          // this will be set on the root instance on initial mount.
          // 保存app的context到vnode的appContext属性中
          vnode.appContext = context
          
          // ...
          
          if (isHydrate && hydrate) {
          // 这里是进行服务端渲染的
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            // 调用render函数,并且将vnode和container传递进去
            render(vnode, rootContainer, isSVG)
          }
          // 将isMounted设置为true
          isMounted = true
          app._container = rootContainer
          // for devtools and telemetry
          ;(rootContainer).__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__) {
        // 如果isMounted为true,并且是在开发环境下,还调用app.mount,那么会发出警告。
          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)\``
          )
        }
      },

mount中调用的render函数

  • 如果vnode为null并且container._vnode属性不为undefined,那么unmount该节点。
  • 如果vnode不为null进行patch。
const render = (vnode, container, isSVG) => {
    if (vnode == null) {
      if (container._vnode) {
        // 如果vnode为null并且container有 ._vnode属性,那么将._vnode移除掉
        unmount(container._vnode, null, null, true)
      }
    } else {
      // vnode有值,进行patch
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }

vue源码的其他阅读笔记

Vue.createApp的基本流程

diff算法