开始

我们都知道的vue3基础用法:

  1. <div id="app">
  2. <button id="show-modal" @click="showModal = true">Show Modal</button>
  3. </div>
  4. <script>
  5. Vue.createApp({
  6. components: { Modal },
  7. data: () => ({
  8. showModal: false
  9. })
  10. }).mount('#app')
  11. </script>

那么createApp是做了什么呢,是怎么样把虚拟DOM转换成真实DOM的呢?
带着这些疑问,我们来慢慢探究。

createApp 源码

const createApp = (...args) => {
  const app = ensureRenderer().createApp(...args)
  console.log('createApp.args>>>', ...args)
  console.log('createApp.app>>>', app)

  const { mount } = app
  app.mount = (containerOrSelector) => {
    const container = normalizeContainer(containerOrSelector)
        container.innerHTML = ''
    mount(container)
  }
  return app
}

我们看看打印的数据是什么样的
image.png

看看ensureRenderer().createApp 做了哪些操作。

function ensureRenderer() {
  return renderer || (renderer = createRenderer(rendererOptions))
}

function createRenderer(options) {
  return baseCreateRenderer(options)
}

看样子 baseCreateRenderer才是真容。

baseCreateRenderer

function baseCreateRenderer(options, createHydrationFns) {
  // 下面全是封装的dom操作方法
    const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    forcePatchProp: hostForcePatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent,
  } = options

  // vue的diff过程叫做patch过程,这个方法是核心,下面会讲
  const patch = ( n1, n2, container,  anchor = null, parentComponent = null, parentSuspense = null, isSVG = false,  optimized = false) => {};

  // 处理文本
  const processText = (n1, n2, container, anchor) => {}

  // 处理element
  const processElement = ( n1, n2, container, ... ) => {}
  // 下面的方法实在是太多了,就没有写入参

  // 处理 fragment
  const processFragment = () => {}

  // 处理组件
  const processComponent = () => {}

  // 挂载element
  const mountElement = () => {}

  // 挂载组件
  const mountComponent = () => {}

  // 更新组件
  const updateComponent = () => {}

  // 挂载子节点
  const mountChildren = () => {}

  // 更新element
  const patchElement = () => {}

  // 更新子节点
  const patchChildren = () => {}

  // 运行带有副作用的render函数
  const setupRenderEffect = () => {}

  // diff,数组子节点发生变更,主要是,更新、删除、添加、移动几种方式处理
  const patchKeyedChildren = () => {}

  // 移动或插入子节点
  const move = () => {}

  // 卸载
  const unmount = () => {}

  // 卸载所有子组件
  const unmountComponent = () => {}

  // 卸载所有子节点
  const unmountChildren = () => {}

  // 渲染和挂载的流程
  const render = (vnode, container) => {
    // 没有vnode,卸载
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      // 创建或更新组件
      patch(container._vnode || null, vnode, container)
    }
    // 缓存vnode
    container._vnode = vnode
  }

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

因该方法里面太多处理了,就没有全部列出来,后续再慢慢更新!
继续往下看createAppAPI

createAppAPI

function createAppAPI(render){
  return function createApp(rootComponent, rootProps = null) {
      // rootComponent 就是上面打印的 'createApp.args>>>',也就是options

    const context = createAppContext()
    const app = (context.app = {
      _uid: uid$1++,
      _component: rootComponent,
      _props: rootProps,
      _container: null,
      _context: context,
      get config() {
        return context.config
      },
      set config(v) {},

      // 这里加载插件,和vue2不同的是,vue2的插件是全局的,这里只针对一个vue实例
      use(plugin, ...options) {},

      // 混入
      mixin(mixin){},

      // 加载组件
      component(mixin){},

      // 指令
      directive(name, directive){},

      // 挂载,核心渲染逻辑
      mount(rootContainer, isHydrate){},

      // 卸载
      unmount(){},

      // 注入
      provide(){}

    })
  }
}

上面代码中的 mount 就是我们这篇的重点:
继续看代码:

// 挂载,核心渲染逻辑
mount = (rootContainer, isHydrate) => {
  // 判断是否已挂载
  if (!isMounted) {
    // 创建虚拟节点
    const vnode = createVNode(rootComponent, rootProps)

    // 在根VNode上存储应用程序context
    vnode.appContext = context

    // 将虚拟节点渲染成真实dom
    render(vnode, rootContainer)

    isMounted = true
    app._container = rootContainer
    rootContainer.__vue_app__ = app
    return vnode.component.proxy
  } else {

  }
}

上面的 render就是 baseCreateRenderer方法中的 render

总结调用流程:
createApp -> ensureRenderer -> baseCreateRenderer -> createAppAPI -> createAppContext -> mount -> render -> patch

下篇我们讲 patch。