我们先从一个最简单的例子来分析渲染流程:

<html>
  <head>
    <style type="text/css">
    </style>
  </head>

  <body>
    <div id="app">
      <div>hello {{state.msg}}</div>
    </div>
    <script src="../dist/vue.global.js"></script>
    <script>
      const {createApp, reactive, onBeforeMount, onBeforeUpdate, onUpdated, onMounted, onRenderTracked, onRenderTriggered} = Vue

      const app = createApp({
        setup(){                 
          const state = reactive({
            msg: 'Vue3.0'
          })          
          return {state}
        }
      }).mount('#app')          
    </script>
  </body>
</html>

上面例子代码输出一行字符hello Vue3.0,我们看看怎么一步一步渲染到页面的。

 1. 从createApp函数开始,看一下这个函数的实现:

export const createApp = ((...args) => {
  //1. 创建app对象
  const app = ensureRenderer().createApp(...args)

  if (__DEV__) {
    injectNativeTagCheck(app)
  }

  const { mount } = app
  //2. 重写app对象的mount方法,重写这个方法的原因主要是为了跨平台。
  //   app对象的mount方法是一个标准的跨平台渲染方法(创建VNode,渲染VNode)
  //   在Web平台下渲染初的VNode挂载到Dom对象上,而在小程序其他平台上可能要挂载到其他对象上
  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) {
      component.template = container.innerHTML
    }
    // clear content before mounting
    container.innerHTML = ''
    const proxy = mount(container)
    if (container instanceof Element) {
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

这个函数比较短小,主要做两件事,第一件是创建app对象,第二件是重写app对象的mount方法,至于为什么要重写mount方法,已在注释中说明。最后返回这个app对象。

2. 接下来继续看创建app对象过程。只有一行代码const app = ensureRenderer().createApp(...args), ensureRenderer函数是用来创建一个渲染器,然后调用这个渲染器的createApp方法。ensureRenderer 创建渲染器方法也是为了跨平台考虑的,Web平台有自己的渲染器,小程序其他平台可能要用其他渲染器。类似插件的形式,各个渲染器最后都要提供createApp方法。ensureRenderer函数也比较简单:

   

function ensureRenderer() {
  console.log(`liubbc ensureRenderer 01`)
  //调用createRenderer方法创建渲染器,参数是renderOptions,可以根据不同的平台传递不同的参数
  //Web平台就是document.createElement, document.createTextNode等方法
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}


const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)

//这里就是renderOptions参数(在Web平台下提供的方法,在其他平台下就需要提供另外的方法了)
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
  },

  //.....

 createElement: (tag, isSVG, is): Element =>
    isSVG
      ? doc.createElementNS(svgNS, tag)
      : doc.createElement(tag, is ? { is } : undefined),

  createText: text => doc.createTextNode(text),

}

    这里我们给创建渲染器函数createRenderer函数提供的参数是Web平台下的。

3. 接下来我们看看,createRenderer创建通用渲染器流程。

    

export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  console.log(`liubbc createRenderer 01`)
  //调用baseCreateRenderer函数创建渲染器
  return baseCreateRenderer<HostNode, HostElement>(options)
}

// implementation
function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {
  // compile-time feature flags check
  if (__ESM_BUNDLER__ && !__TEST__) {
    initFeatureFlags()
  }
  //先把传递过来的Web平台下的渲染器方法解构出来
  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

  //....

  //定义的render函数,最后要把这个函数给export出去,这个函数有两个参数,vnode就是要渲染的
  //虚拟node,container就是要挂载的地方
  const render: RootRenderFunction = (vnode, container) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      console.log(`liubbc patch 02`)
      //patch方法用来首次渲染,或非首次diff更新
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }

  //...
  //最后返回一个对象,这个对象提供了createApp函数,而createApp函数是createAppAPI函数返回值
  //而且createAppAPI函数的参数是render函数
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}


export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  //createAppAPI方法只是一个包装函数,直接返回一个createApp函数
  return function createApp(rootComponent, rootProps = null) {
    if (rootProps != null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }

    const context = createAppContext()
    const installedPlugins = new Set()

    let isMounted = false
    //在这里定义了app对象,只是一个Object对象而已,这个对象提供了一些方法,就包括mount方法
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null,
      _context: context,

      //app对象提供的mount方法,也只有调用这个方法之后才进行开始渲染,所以我们也可以认为这个
      //函数是渲染起点
      mount(rootContainer: HostElement, isHydrate?: boolean): any {
        if (!isMounted) {
          //在这里创建虚拟Node节点
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
          // store app context on the root VNode.
          // this will be set on the root instance on initial mount.
          vnode.appContext = context

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

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            //在这里调用createAppAPI传递进来的参数render方法,开始进行对VNode的渲染
            render(vnode, rootContainer)
          }
          isMounted = true

          //...
     
       }
       //最后要返回这个app对象
       return app
    }

上面的创建渲染器过程还是比较绕的,还是主要为了跨平台考虑的。采用的方法,主要是函数作为参数,以及返回值为函数这种函数式编程思想。到这来创建渲染器过程就分析完了。

4. 最后再看看调用mount方法的地方,也就是文章刚开始我们最简单例子调用的地方。

    

const app = createApp({
        setup(){                 
          const state = reactive({
            msg: 'Vue3.0'
          })          
          return {state}
        }
      }).mount('#app')

 我们再把这个流程串联起来:

  调用createApp返回app对象    ------>   调用app对象重写的mount方法   -----> 对要挂载的对象标准化,然后调用app对象提供的mount方法    ------>   创建VNode,初次渲染或非初次diff更新