刺穿Vue3.*的初始化全过程

1,200 阅读17分钟

刺穿Vue3.*的初始化全过程

开始

首先我们先来观察一下整体的项目结构

image.png

大致先判断一下每个文件的作用,如果暂时没办法判断就先跳过

  • compiler-core AST解析相关的内容
  • compiler-dom 语法树转换成生成DOM树相关的内容
  • compiler-sfc 单文件组件解析相关内容
  • compiler-ssr 服务端渲染编译相关
  • reactivity 响应性API相关
  • vue 3.*入口文件
  • vue-compat 2.*与3.*的兼容版本入口

vue

接着我们跟着一次完成的初始化的过程来看下去,首先我们先来通过vue-cli来拉取项目的模版

$ vue create hello-world
// wait
$ cd hello-world
$ npm run serve

访问http://localhost:8080/就可以看到下图界面 image.png

我们打开项目文件,查看文件

  • src/main.js
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
  • src/App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

  • src/components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>
      For a guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>
    <h3>Installed CLI Plugins</h3>
    <ul>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
    </ul>
    <h3>Essential Links</h3>
    <ul>
      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
    </ul>
    <h3>Ecosystem</h3>
    <ul>
      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

  • public/index.html
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

好的,那么接下来我们先看到main.js,主要就做了三件事情

  1. 引入createApp
  2. 引入App.vue
  3. 执行createApp传入App,链式调用mount传入#app

看到这里很多小伙伴就会问了,为什么先看main.js,那我建议你先看看vue-cli官方文档如果还是不能理解就看看这个webpack官方文档, 回来后我们继续,在vue-next项目中也就是3.*的项目代码中找到先去看packages/vue/src/index.ts

// 简略结构
import { compile, CompilerOptions, CompilerError } from '@vue/compiler-dom'
import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
import * as runtimeDom from '@vue/runtime-dom'

function compileToFunction(
  template: string | HTMLElement,
  options?: CompilerOptions
): RenderFunction {
    // 后续在看
}
registerRuntimeCompiler(compileToFunction)

export { compileToFunction as compile }
export * from '@vue/runtime-dom'

文件中透露两个信息

  1. 在这里注册了全局的运行时编译函数
  2. createApp(...)不在这里,可能在runtime-dom

runtime-dom

入口

我们看到packages/runtime-dom/src/index.ts中的内容,在代码上我直接通过备注的方式来分析一下这块的内容

import {
  createRenderer,
  createHydrationRenderer,
  warn,
  RootRenderFunction,
  CreateAppFunction,
  Renderer,
  HydrationRenderer,
  App,
  RootHydrateFunction,
  isRuntimeOnly,
  DeprecationTypes,
  compatUtils
} from '@vue/runtime-core'
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'
import {
  isFunction,
  isString,
  isHTMLTag,
  isSVGTag,
  extend,
  NOOP
} from '@vue/shared'
/*
*  渲染配置项,里面包括patchProp(...) 跟 nodeOps下的一些操作内容
*  nodeOps 下主要是一些具体的节点操作方法
*  patchProp 看起来更像是对 dom attrs的解析 以及 操作
*/ 
const rendererOptions = extend({ patchProp }, nodeOps)
let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer
/*
* renderer的获取方法,主要跟单例相关以及服务端渲染的渲染器选择相关
*/ 
function ensureRenderer() {
  return (
    renderer ||
     // 如果不存在就通过createRenderer方法传入rendererOptions创建renderer
    (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  )
}
export const createApp = ((...args) => {
  /*
  * 获取renderer调用上面的createApp方法,参数为App.vue,当然经过SFC的解析, 已经变成了下图的结构
  * 但是我们为了更好的了解源码,修改main文件代码如下
  * args变成
  * {
  *    name: 'App',
  *    template: `
  *      <img alt="Vue logo" src="./assets/logo.png">
  *      <HelloWorld></HelloWorld>
  *    `,
  *    components: {
  *      HelloWorld
  *    }
  * }
  * 
  */
  const app = ensureRenderer().createApp(...args)
  // 调试代码,先行跳过
  if (__DEV__) {
    injectNativeTagCheck(app)
    injectCompilerOptionsCheck(app)
  }
  // 取出mount在外重新包装一层新的mount方法
  const { mount } = app
  // 如下所示 containerOrSelector 为 '#app'
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
     // 根据#app获取对应的DOM对象
    const container = normalizeContainer(containerOrSelector)
    if (!container) return
     // 通过调试node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js 文件 发现实际上这个就是 args 传入的配置项信息,如下图
    const component = app._component
    // 当component参数不为函数,也不存在render函数以及template时获取dom对象的innerHTML为模版
    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
          }
        }
      }
    }

    // clear content before mounting
    // 清空原来的内容模版
    container.innerHTML = ''
    // 执行原挂在方法
    const proxy = mount(container, false, container instanceof SVGElement)
    if (container instanceof Element) {
      // 移除v-cloak,不了解v-cloak作用的可以看https://v3.cn.vuejs.org/api/directives.html#v-cloak
      container.removeAttribute('v-cloak')
      // 设置data-v-app标识
      container.setAttribute('data-v-app', '')
    }
    return proxy
  }

  return app
})
  • 经过loader处理后的数据结构图一

  • 如上备注修改main文件代码

import { createApp } from 'vue/dist/vue.esm-bundler.js'
import HelloWorld from './components/HelloWorld.vue'
createApp({
  name: 'App',
  template: `
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld></HelloWorld>
  `,
  components: {
    HelloWorld
  }
})
  .mount('#app')

  • 调试看到的component 变量信息图二

思考:基本上createApp方法的mount只是做了一层包装,实际的挂载应该来源于createRenderer返回的mount方法中,我们需要深入进去,看具体实现

runtime-core(前半程)

实例化过程

由于当前文件中的代码量比较大,从这里开始按照函数给大家放代码

// 文件runtime-core/src/renderer.ts
import { createAppAPI, CreateAppFunction } from './apiCreateApp'
// 也是做了一层包装,实际上调用的是baseCreateRenderer方法
export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}

function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {

// 解构并重命名节点操作方法,之前提到过,来源于patchProp 和 nodeOps
 const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options

  // 这中间省略超多方法,后续再回来看,主要是patch

  const render: RootRenderFunction = (vnode, container, isSVG) => {
    // 如果vnode为空,但是我们这里不为空
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      // 我们这里就直接进入patch函数
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }
  
  return {
    render,
    // 由于本篇不涉及,所以不提及感兴趣可以自行了解
    hydrate,
    // 那么这里其实还是做了render的闭包,然后将render传入createAppAPI中
    createApp: createAppAPI(render, hydrate)
  }
}
  
export 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()
  }
}

// 切换到文件runtime-core/src/apiCreateApp.ts
// 全局应用ID 为了保证ID的唯一性,全局唯一自增
let uid = 0

export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    if (rootProps != null && !isObject(rootProps)) {
      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,

      get config() {
        return context.config
      },

      set config(v) {
        if (__DEV__) {
          warn(
            `app.config cannot be replaced. Modify individual options instead.`
          )
        }
      },

      use(plugin: Plugin, ...options: any[]) {
        if (installedPlugins.has(plugin)) {
          __DEV__ && warn(`Plugin has already been applied to target app.`)
        } else if (plugin && isFunction(plugin.install)) {
          installedPlugins.add(plugin)
          plugin.install(app, ...options)
        } else if (isFunction(plugin)) {
          installedPlugins.add(plugin)
          plugin(app, ...options)
        }
        return app
      },

      mixin(mixin: ComponentOptions) {
        if (__FEATURE_OPTIONS_API__) {
          if (!context.mixins.includes(mixin)) {
            context.mixins.push(mixin)
          } 
        }
        return app
      },

      component(name: string, component?: Component): any {
        if (!component) {
          return context.components[name]
        }
        context.components[name] = component
        return app
      },

      directive(name: string, directive?: Directive) {
        if (!directive) {
          return context.directives[name] as any
        }
        context.directives[name] = directive
        return app
      },
      // 关注挂载的原mount方法
      mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
        if (!isMounted) {
          // 传入createApp的初始参数,返回Vnode如下图结构
          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

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            // 这里又调用回了上文的render方法
            /*
            *
            */
            render(vnode, rootContainer, isSVG)
          }
          isMounted = true
          app._container = rootContainer
          // for devtools and telemetry
          ;(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
        }
      },

      unmount() {
        if (isMounted) {
          render(null, app._container)
          if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            app._instance = null
            devtoolsUnmountApp(app)
          }
          delete app._container.__vue_app__
        }
      },

      provide(key, value) {
        // TypeScript doesn't allow symbols as index type
        // https://github.com/Microsoft/TypeScript/issues/24587
        context.provides[key as string] = value
        return app
      }
    })
    // 兼容2.*版本
    if (__COMPAT__) {
      installAppCompatProperties(app, context, render)
    }

    return app
  }
}

// 切换到文件packages/runtime-core/src/vnode.ts
function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  // 我们这里type依然是传入我们的构造的配置项目
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    if (__DEV__ && !type) {
      warn(`Invalid vnode type when creating vnode: ${type}.`)
    }
    type = Comment
  }
  // 是否是Vnode节点
  if (isVNode(type)) {
    // createVNode receiving an existing vnode. This happens in cases like
    // <component :is="vnode"/>
    // #2078 make sure to merge refs during the clone instead of overwriting it
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children)
    }
    return cloned
  }

  // class component normalization.
  if (isClassComponent(type)) {
    type = type.__vccOpts
  }

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

  // class & style normalization.
  if (props) {
    // for reactive or proxy objects, we need to clone it to enable mutation.
    props = guardReactiveProps(props)!
    let { class: klass, style } = props
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass)
    }
    if (isObject(style)) {
      // reactive state objects need to be cloned since they are likely to be
      // mutated
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
  }

  // encode the vnode type information into a bitmap
  // 我们由于是Object 所以判断的类型是ShapeFlags.STATEFUL_COMPONEN 具体的值 是 4(方便大家在调试中看懂)
  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)) {
    type = toRaw(type)
    warn(
      `Vue received a Component which was made a reactive object. This can ` +
        `lead to unnecessary performance overhead, and should be avoided by ` +
        `marking the component with \`markRaw\` or using \`shallowRef\` ` +
        `instead of \`ref\`.`,
      `\nComponent that was made reactive: `,
      type
    )
  }

  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

function createBaseVNode(
  // 实际上还是最开始的配置信息
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
  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,
    // 4
    shapeFlag,
    // 0
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  } as VNode

  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children)
    // normalize suspense children
    if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
      ;(type as typeof SuspenseImpl).normalize(vnode)
    }
  } else if (children) {
    // compiled element vnode - if children is passed, only possible types are
    // string or Array.
    vnode.shapeFlag |= isString(children)
      ? ShapeFlags.TEXT_CHILDREN
      : ShapeFlags.ARRAY_CHILDREN
  }

  // validate key
  if (__DEV__ && vnode.key !== vnode.key) {
    warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
  }

  // track vnode for block tree
  if (
    isBlockTreeEnabled > 0 &&
    // avoid a block node from tracking itself
    !isBlockNode &&
    // has current parent block
    currentBlock &&
    // presence of a patch flag indicates this node needs patching on updates.
    // component nodes also should always be patched, because even if the
    // component doesn't need to update, it needs to persist the instance on to
    // the next vnode so that it can be properly unmounted later.
    (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    // the EVENTS flag is only for hydration and if it is the only flag, the
    // vnode should not be considered dynamic due to handler caching.
    vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
  ) {
    currentBlock.push(vnode)
  }

  if (__COMPAT__) {
    convertLegacyVModelProps(vnode)
    defineLegacyVNodeProperties(vnode)
  }

  return vnode
}

  • 初始化Vnode的结构 图3

思考:这条链路看下来后主要有以下这么几个点

  • 根据 runtime-core/src/apiCreateApp.ts 中的 createAppAPI 创建 应用上下文 以及 提供应用方法
  • 挂载函数中实际上做了两件事情
    1. 根据根组件创建一个Vnode节点
    2. 调用baseCreateRenderer闭包中的render方法
  • render 函数中主要是调用baseCreateRenderer闭包中的patch方法,那么我们想看到实际的渲染过程,还是要跟进patch方法中查看具体过程
// 文件runtime-core/src/renderer.ts baseCreateRenderer函数的上下文中
// 这里会先补充初始化的参数因为patch本身是一个被递归函数
  const patch: PatchFn = (
    // null
    n1,
    // rootComponent 生成的 vnode节点
    n2,
    // DOM节点
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  ) => {
      
    if (n1 === n2) {
      return
    }

    // patching & not same type, unmount old tree
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }

    // 0 !== 2
    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }

    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text:
        processText(n1, n2, container, anchor)
        break
      case Comment:
        processCommentNode(n1, n2, container, anchor)
        break
      case Static:
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      case Fragment:
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        break
      default:
        // 这里的&就是按位与 00010 与 00011 的 结果就是 00010
        if (shapeFlag & ShapeFlags.ELEMENT) {
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          // 首次命中进入这个逻辑,我们深入进去
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(
            n1 as TeleportVNode,
            n2 as TeleportVNode,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          ;(type as typeof SuspenseImpl).process(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, `(${typeof type})`)
        }
    }

    // set ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
    }
  }
  const processComponent = (
    // null
    n1: VNode | null,
    // rootComponent 生成的 vnode节点
    n2: VNode,
    // #app的DOM节点
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    // null
    n2.slotScopeIds = slotScopeIds
    if (n1 == null) {
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        ;(parentComponent!.ctx as KeepAliveContext).activate(
          n2,
          container,
          anchor,
          isSVG,
          optimized
        )
      } else {
        //命中挂在组件的函数逻辑,我们深入进去
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
    } else {
      updateComponent(n1, n2, optimized)
    }
  }
  
  const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // 2.x compat may pre-create the component instance before actually
    // mounting
    const compatMountInstance =
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      // 这里我们会命中构建一个新的组件实例的逻辑 initialVNode.component 的结构如下图
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))

    if (__DEV__ && instance.type.__hmrId) {
      registerHMR(instance)
    }

    if (__DEV__) {
      pushWarningContext(initialVNode)
      startMeasure(instance, `mount`)
    }

    // inject renderer internals for keepAlive
    if (isKeepAlive(initialVNode)) {
      ;(instance.ctx as KeepAliveContext).renderer = internals
    }

    // resolve props and slots for setup context
    if (!(__COMPAT__ && compatMountInstance)) {
      if (__DEV__) {
        startMeasure(instance, `init`)
      }
      // 关键组件初始化方法,包括组件中所有相关配置信息,声明周期的的初始化过程,render函数的处理过程
      setupComponent(instance)
      if (__DEV__) {
        endMeasure(instance, `init`)
      }
    }

    // setup() is async. This component relies on async logic to be resolved
    // before proceeding
    if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
      parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)

      // Give it a placeholder if this is not hydration
      // TODO handle self-defined fallback
      if (!initialVNode.el) {
        const placeholder = (instance.subTree = createVNode(Comment))
        processCommentNode(null, placeholder, container!, anchor)
      }
      return
    }
    // 渲染副作用的挂载
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )

    if (__DEV__) {
      popWarningContext()
      endMeasure(instance, `mount`)
    }
  }

// 切换到runtime-core/src/component.ts
export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  const type = vnode.type as ConcreteComponent
  // inherit parent app context - or - if root, adopt from root vnode
  const appContext =
    (parent ? parent.appContext : vnode.appContext) || emptyAppContext

  const instance: ComponentInternalInstance = {
    uid: uid++,
    vnode,
    type,
    parent,
    appContext,
    root: null!, // to be immediately set
    next: null,
    subTree: null!, // will be set synchronously right after creation
    effect: null!,
    update: null!, // will be set synchronously right after creation
    scope: new EffectScope(true /* detached */),
    render: null,
    proxy: null,
    exposed: null,
    exposeProxy: null,
    withProxy: null,
    provides: parent ? parent.provides : Object.create(appContext.provides),
    accessCache: null!,
    renderCache: [],

    // local resovled assets
    components: null,
    directives: null,

    // resolved props and emits options
    propsOptions: normalizePropsOptions(type, appContext),
    emitsOptions: normalizeEmitsOptions(type, appContext),

    // emit
    emit: null!, // to be set immediately
    emitted: null,

    // props default value
    propsDefaults: EMPTY_OBJ,

    // inheritAttrs
    inheritAttrs: type.inheritAttrs,

    // state
    ctx: EMPTY_OBJ,
    data: EMPTY_OBJ,
    props: EMPTY_OBJ,
    attrs: EMPTY_OBJ,
    slots: EMPTY_OBJ,
    refs: EMPTY_OBJ,
    setupState: EMPTY_OBJ,
    setupContext: null,

    // suspense related
    suspense,
    suspenseId: suspense ? suspense.pendingId : 0,
    asyncDep: null,
    asyncResolved: false,

    // lifecycle hooks
    // not using enums here because it results in computed properties
    isMounted: false,
    isUnmounted: false,
    isDeactivated: false,
    bc: null,
    c: null,
    bm: null,
    m: null,
    bu: null,
    u: null,
    um: null,
    bum: null,
    da: null,
    a: null,
    rtg: null,
    rtc: null,
    ec: null,
    sp: null
  }
  if (__DEV__) {
    instance.ctx = createDevRenderContext(instance)
  } else {
    instance.ctx = { _: instance }
  }
  instance.root = parent ? parent.root : instance
  instance.emit = emit.bind(null, instance)

  // apply custom element special handling
  if (vnode.ce) {
    vnode.ce(instance)
  }

  return instance
}
  • initialVNode.component 的数据结构以及数据值 image.png image.png

思考:patch方法本身更多是一个分发,针对不同的节点类型做不同的处理,那么由于我们是组件节点,我们跟到mountComponent方法中,mountComponent方法主要做了三件事情,

  • createComponentInstance 创建组件根部
  • setupComponent 设置组件(包括状态初始化,声明周期等)
  • setupRenderEffect 设置渲染的副作用 (包括响应api的副作用挂载) 对于setupComponent 和 setupRenderEffect我们还没具体介绍,那么我们先来看一下,setupComponent的内部实现
// runtime-core/src/component.ts
export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR
  /*
  * 当前的props 根 children都是null,这里的props主要是 给根组件的props
  */
  const { props, children } = instance.vnode
  // isStateful = 4
  const isStateful = isStatefulComponent(instance)
  // 初始化组件的props
  initProps(instance, props, isStateful, isSSR)
  // 初始化slots姐弟哪
  initSlots(instance, children)
  // 这里如果是setup周期为promise那才会有一个setupResult的放回不然就是undefined
  const setupResult = isStateful
      // 命中setupStatefulComponent方法继续做设置
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

function setupStatefulComponent(
  // 组件实例
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions

  // 中间省略部分调试代码
  
  // 0. create render proxy property access cache
  instance.accessCache = Object.create(null)
  
  // 1. create public instance / render proxy
  // also mark it raw so it's never observed
  // 这里针对组件的上下文也就是组件内部this.*的this进行代理设置
  instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
  
  // 2. call setup()
  // 取出setup方法,但是我们目前案例中没有setup所以我们先YY来进行结果预测
  const { setup } = Component
  if (setup) {
    // setupContext的初始化,就是setup(props, context) 的二个参数的初始化
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)
    // 设置instance 到 currentInstance(模块闭包变量)
    // 并且把当前组件的副作用域推入副作用域栈
    setCurrentInstance(instance)
    // 暂停追踪
    pauseTracking()
    /*
    * callWithErrorHandling执行了setup方法
    * setupResult为set周期中具体放回的可响应方法以及可响应数据
    * 反过来说其实在这一步,setup周期内的可响应数据已经采集到了对应的依赖
    * 这里可能会需要具体根据现有的全局状态去分析 响应API的具体实现,这个放在后面去看
    */
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    // 重启追踪
    resetTracking()
    // 推出当前副作用域
    unsetCurrentInstance()

    // 判断是否为Promise
    if (isPromise(setupResult)) {
      setupResult.then(unsetCurrentInstance, unsetCurrentInstance)

      if (isSSR) {
        // return the promise so server-renderer can wait on it
        return setupResult
          .then((resolvedResult: unknown) => {
            handleSetupResult(instance, resolvedResult, isSSR)
          })
          .catch(e => {
            handleError(e, instance, ErrorCodes.SETUP_FUNCTION)
          })
      } else if (__FEATURE_SUSPENSE__) {
        // async setup returned Promise.
        // bail here and wait for re-entry.
        instance.asyncDep = setupResult
      } else if (__DEV__) {
        warn(
          `setup() returned a Promise, but the version of Vue you are using ` +
            `does not support it yet.`
        )
      }
    } else {
      // 处理同步的setup结果
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}
export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
  // 如果setup执行返回一个函数,并且不是在SSR的情况下的话,那么当前函数就会作为函数的渲染函数进行使用 https://v3.cn.vuejs.org/api/composition-api.html#setup 
  if (isFunction(setupResult)) {
    // setup returned an inline render function
    if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
      // when the function's name is `ssrRender` (compiled by SFC inline mode),
      // set it as ssrRender instead.
      instance.ssrRender = setupResult
    } else {
      instance.render = setupResult as InternalRenderFunction
    }
    // 如果是对象返回那么就给对象增加代理的数据处理能力 proxyRefs(语法糖,根据不同的数据类型给予不同的数据赋值方法罢了)
  } else if (isObject(setupResult)) {
    if (__DEV__ && isVNode(setupResult)) {
      warn(
        `setup() should not return VNodes directly - ` +
          `return a render function instead.`
      )
    }
    // setup returned bindings.
    // assuming a render function compiled from template is present.
    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
      instance.devtoolsRawSetupState = setupResult
    }
    // proxyRefs 语法糖,根据不同的数据类型给予不同的数据set跟get方法
    instance.setupState = proxyRefs(setupResult)
    if (__DEV__) {
      exposeSetupStateOnRenderContext(instance)
    }
  } else if (__DEV__ && setupResult !== undefined) {
    warn(
      `setup() should return an object. Received: ${
        setupResult === null ? 'null' : typeof setupResult
      }`
    )
  }
  // 完成组件的设置
  finishComponentSetup(instance, isSSR)
}
export function finishComponentSetup(
  // 组件实例
  instance: ComponentInternalInstance,
  // false
  isSSR: boolean,
  skipOptions?: boolean
) {
  const Component = instance.type as ComponentOptions

  if (__COMPAT__) {
    // 兼容之前Vue2.*的渲染模式 render(h)作为参数的场景
    convertLegacyRenderFn(instance)

    if (__DEV__ && Component.compatConfig) {
      validateCompatConfig(Component.compatConfig)
    }
  }

  // template / render function normalization
  // 模板 / 渲染函数规范化
  // could be already set when returned from setup()
  // 如果没有渲染函数,也就是第一优先级为渲染函数
  if (!instance.render) {
    // only do on-the-fly compile if not in SSR - SSR on-the-fly compliation
    // 如果不是在SSR-SSR动态编译中,则只进行动态编译
    // is done by server-renderer
    /*
    *   不是服务端渲染
    *。 而且存在全局编译器(Tips:这里的compile来源于 packages/vue/src/index.ts 的 compileToFunction)
    *。 并且组件具体配置项目中不包含render函数
    */ 
    if (!isSSR && compile && !Component.render) {
      const template =
        (__COMPAT__ &&
          instance.vnode.props &&
          instance.vnode.props['inline-template']) ||
          // Vue3.*主要走下面这个,上面主要是兼容Vue2.* inline-template 模版功能
        Component.template
      if (template) {
        if (__DEV__) {
          startMeasure(instance, `compile`)
        }
        const {
          // 用于判断模版中的自定义标签,已经废弃了,合并到compilerOptions中
          isCustomElement,
          // 配置运行时编译器的选项,包括标签,模版值渲染的这一类定义
          compilerOptions
        } = instance.appContext.config
        const {
          // 模版值渲染的配置项
          // 估计也是废弃了应为也是合并到了compilerOptions中
          delimiters,
          compilerOptions: componentCompilerOptions 
        } =Component
        // 合并编译配置项
        const finalCompilerOptions: CompilerOptions = extend(
          extend(
            {
              isCustomElement,
              delimiters
            },
            compilerOptions
          ),
          componentCompilerOptions
        )
        if (__COMPAT__) {
          // pass runtime compat config into the compiler
          // compatConfig 配置项目 https://v3.cn.vuejs.org/guide/migration/migration-build.html#%E5%9F%BA%E4%BA%8E%E5%8D%95%E4%B8%AA%E7%BB%84%E4%BB%B6%E7%9A%84%E9%85%8D%E7%BD%AE
          finalCompilerOptions.compatConfig = Object.create(globalCompatConfig)
          if (Component.compatConfig) {
            extend(finalCompilerOptions.compatConfig, Component.compatConfig)
          }
        }
        /* 
        * 通过模版以及finalCompilerOptions 还有 compile 输出渲染函数 具体看向 packages/compiler-dom/src/index.ts
        * compile的注册以及内部流程我们放到后面去看, 根据类型,可以知道会返回一个 CompileFunction 类型的函数
        */  
        Component.render = compile(template, finalCompilerOptions)
        if (__DEV__) {
          endMeasure(instance, `compile`)
        }
      }
    }
    // 类型转换以及判断是否存在render函数
    instance.render = (Component.render || NOOP) as InternalRenderFunction

    // for runtime-compiled render functions using `with` blocks, the render
    // proxy used needs a different `has` handler which is more performant and
    // also only allows a whitelist of globals to fallthrough.
    if (installWithProxy) {
      installWithProxy(instance)
    }
  }

  // support for 2.x options
  // 这里主要是为了支持2.*的option的方式编写的代码
  if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
    // 设置自身的上下文为活跃上下文
    setCurrentInstance(instance)
    // 暂停追踪
    pauseTracking()
    // 初始化所有相关的option中的配置信息,
    applyOptions(instance)
    // 重启追踪
    resetTracking()
    // 推出上下文栈
    unsetCurrentInstance()
  }
}

思考:这里可以发现关键点在于compiler(...) 以及 applyOptions(...),但是compiler涉及到较多的内容,所以我们这里优先看applyOptions 然后 后面的篇幅在介绍compiler

// runtime-core/src/componentOptions.ts
/**
 * 支持 2.* vue option 初始化的关键函数
 * @param  {ComponentInternalInstance} instance
 */
export function applyOptions(instance: ComponentInternalInstance) {
  // 解析基本的component option
  const options = resolveMergedOptions(instance)
  // 上文有提及的上下文代理
  const publicThis = instance.proxy! as any
  // 上下文原对象
  const ctx = instance.ctx

  // do not cache property access on public proxy during state initialization
  // 是否要缓存资源标识,这个主要运用于上下文代理的取值判断
  shouldCacheAccess = false

  // call beforeCreate first before accessing other options since
  // the hook may mutate resolved options (#2791)
  // 如果有beforeCreate的声明周期函数,那么调用对应的声明周期
  if (options.beforeCreate) {
    callHook(options.beforeCreate, instance, LifecycleHooks.BEFORE_CREATE)
  }

  const {
    // state
    data: dataOptions,
    computed: computedOptions,
    methods,
    watch: watchOptions,
    provide: provideOptions,
    inject: injectOptions,
    // lifecycle
    created,
    beforeMount,
    mounted,
    beforeUpdate,
    updated,
    activated,
    deactivated,
    beforeDestroy,
    beforeUnmount,
    destroyed,
    unmounted,
    render,
    renderTracked,
    renderTriggered,
    errorCaptured,
    serverPrefetch,
    // public API
    expose,
    inheritAttrs,
    // assets
    components,
    directives,
    filters
  } = options

  const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null

  if (__DEV__) {
    const [propsOptions] = instance.propsOptions
    if (propsOptions) {
      for (const key in propsOptions) {
        checkDuplicateProperties!(OptionTypes.PROPS, key)
      }
    }
  }

  // options initialization order (to be consistent with Vue 2):
  // - props (already done outside of this function)
  // - inject
  // - methods
  // - data (deferred since it relies on `this` access)
  // - computed
  // - watch (deferred since it relies on `this` access)
  // 解析 inject 方法,贴在下方
  if (injectOptions) {
    resolveInjections(
      injectOptions,
      ctx,
      // null
      checkDuplicateProperties,
      instance.appContext.config.unwrapInjectedRef
    )
  }
  // 解析 methods 对象
  if (methods) {
    for (const key in methods) {
      const methodHandler = (methods as MethodOptions)[key]
      if (isFunction(methodHandler)) {
          ctx[key] = methodHandler.bind(publicThis)
      }
    }
  }
  
  if (dataOptions) {
    // 首先要求option中的data必须是方法
    const data = dataOptions.call(publicThis, publicThis)
    if (!isObject(data)) {
     warn(`data() should return an object.`)
    } else {
      // 通过响应API的 reactive 进行数据响应式管理
      instance.data = reactive(data)
      if (__DEV__) {
        for (const key in data) {
          checkDuplicateProperties!(OptionTypes.DATA, key)
          // expose data on ctx during dev
          if (key[0] !== '$' && key[0] !== '_') {
            Object.defineProperty(ctx, key, {
              configurable: true,
              enumerable: true,
              get: () => data[key],
              set: NOOP
            })
          }
        }
      }
    }
  }

  // 后续的资源需要缓存
  shouldCacheAccess = true
  // 如果存在自动计算配置
  if (computedOptions) {
    for (const key in computedOptions) {
      const opt = (computedOptions as ComputedOptions)[key]
      // 绑定get的执行上下文
      const get = isFunction(opt)
        ? opt.bind(publicThis, publicThis)
        : isFunction(opt.get)
        ? opt.get.bind(publicThis, publicThis)
        : NOOP
      if (__DEV__ && get === NOOP) {
        warn(`Computed property "${key}" has no getter.`)
      }
      // 如果存在Set配置就绑定执行上下文
      const set =
        !isFunction(opt) && isFunction(opt.set)
          ? opt.set.bind(publicThis)
          : __DEV__
          ? () => {
              warn(
                `Write operation failed: computed property "${key}" is readonly.`
              )
            }
          : NOOP
      // 通过响应API的computed进行处理
      const c = computed({
        get,
        set
      })
      Object.defineProperty(ctx, key, {
        enumerable: true,
        configurable: true,
        get: () => c.value,
        set: v => (c.value = v)
      })
      if (__DEV__) {
        checkDuplicateProperties!(OptionTypes.COMPUTED, key)
      }
    }
  }
  /**
   * 基于option.watch进行watch的初始化
   */
  if (watchOptions) {
    for (const key in watchOptions) {
      // 传入解析以及 通过响应API的watch进行初始化
      createWatcher(watchOptions[key], ctx, publicThis, key)
    }
  }

   /**
   * 基于option.provide进行provide的初始化
   */
  if (provideOptions) {
    const provides = isFunction(provideOptions)
      ? provideOptions.call(publicThis)
      : provideOptions
    Reflect.ownKeys(provides).forEach(key => {
      // 通过响应API的provide进行处理
      provide(key, provides[key])
    })
  }
  // created生命周期调用,完成状态的初始化后
  if (created) {
    callHook(created, instance, LifecycleHooks.CREATED)
  }
  // 声明周期调用的语法糖
  function registerLifecycleHook(
    register: Function,
    hook?: Function | Function[]
  ) {
    if (isArray(hook)) {
      hook.forEach(_hook => register(_hook.bind(publicThis)))
    } else if (hook) {
      register((hook as Function).bind(publicThis))
    }
  }
  /*
  * 生命周期,目前也是通过具体的API方法进行调用,在这里先对声明周期方法进行挂载,绑定对应上下文
  */ 
  registerLifecycleHook(onBeforeMount, beforeMount)
  registerLifecycleHook(onMounted, mounted)
  registerLifecycleHook(onBeforeUpdate, beforeUpdate)
  registerLifecycleHook(onUpdated, updated)
  registerLifecycleHook(onActivated, activated)
  registerLifecycleHook(onDeactivated, deactivated)
  registerLifecycleHook(onErrorCaptured, errorCaptured)
  registerLifecycleHook(onRenderTracked, renderTracked)
  registerLifecycleHook(onRenderTriggered, renderTriggered)
  registerLifecycleHook(onBeforeUnmount, beforeUnmount)
  registerLifecycleHook(onUnmounted, unmounted)
  registerLifecycleHook(onServerPrefetch, serverPrefetch)

  if (__COMPAT__) {
    if (
      beforeDestroy &&
      softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance)
    ) {
      registerLifecycleHook(onBeforeUnmount, beforeDestroy)
    }
    if (
      destroyed &&
      softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance)
    ) {
      registerLifecycleHook(onUnmounted, destroyed)
    }
  }
  // vue3.2 新增的状态,在公共组件实例上可以访问的属性,其实有点类似在class中声明 public的感觉
  if (isArray(expose)) {
    if (expose.length) {
      const exposed = instance.exposed || (instance.exposed = {})
      expose.forEach(key => {
        Object.defineProperty(exposed, key, {
          get: () => publicThis[key],
          set: val => (publicThis[key] = val)
        })
      })
    } else if (!instance.exposed) {
      instance.exposed = {}
    }
  }

  // options that are handled when creating the instance but also need to be
  // applied from mixins
  // 如果配置项中有渲染方法,并且实例中的渲染方法为空函数时,那么就采用配置项中的渲染函数
  if (render && instance.render === NOOP) {
    instance.render = render as InternalRenderFunction
  }
  if (inheritAttrs != null) {
    instance.inheritAttrs = inheritAttrs
  }

  // asset options.
  if (components) instance.components = components as any
  if (directives) instance.directives = directives
  if (
    __COMPAT__ &&
    filters &&
    isCompatEnabled(DeprecationTypes.FILTERS, instance)
  ) {
    instance.filters = filters
  }
}
export function resolveMergedOptions(
  instance: ComponentInternalInstance
): MergedComponentOptions {
  // 这里的type实际上还是rootComponent的配置项
  const base = instance.type as ComponentOptions
  // 取出mixins 跟 extends
  const { mixins, extends: extendsOptions } = base
  // 取出APP全局上下文配置项
  const {
    mixins: globalMixins,
    optionsCache: cache,
    config: { optionMergeStrategies }
  } = instance.appContext
  const cached = cache.get(base)

  let resolved: MergedComponentOptions
  // 是否有解析结果的缓存,如果没有
  if (cached) {
    resolved = cached
  } else if (!globalMixins.length && !mixins && !extendsOptions) {
    if (
      __COMPAT__ &&
      isCompatEnabled(DeprecationTypes.PRIVATE_APIS, instance)
    ) {
      resolved = extend({}, base) as MergedComponentOptions
      resolved.parent = instance.parent && instance.parent.proxy
      resolved.propsData = instance.vnode.props
    } else {
      // 解析结果直接等于base 也就是 rootComponent的配置项
      resolved = base as MergedComponentOptions
    }
  } else {
    // 否则这里做配置项合并,下面还会针对不同的option采用不同的合并策略,感兴趣的可以看进去
    resolved = {}
    if (globalMixins.length) {
      globalMixins.forEach(m =>
        mergeOptions(resolved, m, optionMergeStrategies, true)
      )
    }
    mergeOptions(resolved, base, optionMergeStrategies)
  }

  cache.set(base, resolved)
  return resolved
}

/**
 * 解析option配置项目中的inject内容区域
 * @param  {ComponentInjectOptions} injectOptions
 * @param  {any} ctx
 * @param  {} checkDuplicateProperties=NOOPasany
 * @param  {} unwrapRef=false
 */
export function resolveInjections(
  // option的inject的配置项
  injectOptions: ComponentInjectOptions,
  // 组件实例的上下文
  ctx: any,
  // null
  checkDuplicateProperties = NOOP as any,
  unwrapRef = false
) {
  // 如果是类似 ['name'] 定义的方式, 将会格式化成{ name: 'name' }
  if (isArray(injectOptions)) {
    injectOptions = normalizeInject(injectOptions)!
  }
  // 通过循环解析inject配置项,通过inject api方法进行初始化
  for (const key in injectOptions) {
    const opt = (injectOptions as ObjectInjectOptions)[key]
    let injected: unknown
    if (isObject(opt)) {
      if ('default' in opt) {
        injected = inject(
          opt.from || key,
          opt.default,
          true /* treat default function as factory */
        )
      } else {
        injected = inject(opt.from || key)
      }
    } else {
      injected = inject(opt)
    }
    if (isRef(injected)) {
      // TODO remove the check in 3.3
      if (unwrapRef) {
        Object.defineProperty(ctx, key, {
          enumerable: true,
          configurable: true,
          get: () => (injected as Ref).value,
          set: v => ((injected as Ref).value = v)
        })
      } else {
        if (__DEV__) {
          warn(
            `injected property "${key}" is a ref and will be auto-unwrapped ` +
              `and no longer needs \`.value\` in the next minor release. ` +
              `To opt-in to the new behavior now, ` +
              `set \`app.config.unwrapInjectedRef = true\` (this config is ` +
              `temporary and will not be needed in the future.)`
          )
        }
        ctx[key] = injected
      }
    } else {
      ctx[key] = injected
    }
    if (__DEV__) {
      checkDuplicateProperties!(OptionTypes.INJECT, key)
    }
  }
}
// 创建观察者对象
export function createWatcher(
  raw: ComponentWatchOptionItem,
  ctx: Data,
  publicThis: ComponentPublicInstance,
  key: string
) {
  const getter = key.includes('.')
    ? createPathGetter(publicThis, key)
    : () => (publicThis as any)[key]
  if (isString(raw)) {
    const handler = ctx[raw]
    if (isFunction(handler)) {
      watch(getter, handler as WatchCallback)
    } else if (__DEV__) {
      warn(`Invalid watch handler specified by key "${raw}"`, handler)
    }
  } else if (isFunction(raw)) {
    watch(getter, raw.bind(publicThis))
  } else if (isObject(raw)) {
    if (isArray(raw)) {
      raw.forEach(r => createWatcher(r, ctx, publicThis, key))
    } else {
      const handler = isFunction(raw.handler)
        ? raw.handler.bind(publicThis)
        : (ctx[raw.handler] as WatchCallback)
      if (isFunction(handler)) {
        watch(getter, handler, raw)
      } else if (__DEV__) {
        warn(`Invalid watch handler specified by key "${raw.handler}"`, handler)
      }
    }
  } else if (__DEV__) {
    warn(`Invalid watch option: "${key}"`, raw)
  }
}

思考:那么实际上从applyOptions看来,在Vue3.*的option实际上可以理解为语法糖,主要是为了兼容2.*的的用户习惯,在这个方法中,大量的使用了响应式API以及生命周期API,那么为了保证思维的连续性,以及保证现场的还原情况,我们先来看到在响应式api中做了什么,其中我们用reactive(...)作为我们

reactivity

reactive

这里为了方便DEBUG数据,我将main.js文件中的配置修改如下

// main.js
import { createApp } from 'vue/dist/vue.esm-bundler.js'
import HelloWorld from './components/HelloWorld.vue'
const option = {
  name: 'App',
  data() {
    return {
      a: 1
    }
  },
  template: `
    <img alt="Vue logo" src="./assets/logo.png">
    <div>{{a}}</div>
    <HelloWorld></HelloWorld>
  `,
  components: {
    HelloWorld
  }
}
createApp(option).mount('#app')

回到vue-next的源码项目中看到

// reactivity/src/reactive.ts
// 代理对象集合
export const reactiveMap = new WeakMap<Target, any>()
// 响应式api reactive 方法
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    // 具体代理对象
    target,
    // 是否只读,createReactiveObject 同时也设计 isReadonly api的构造所以提供相关配置项
    false,
    // 第一类拦截方法
    mutableHandlers,
    // 第二类拦截方法
    mutableCollectionHandlers,
    reactiveMap
  )
}
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 仅针对typeof是object的对象使用reactive
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  // 如果对象已经是代理对象并且不是只读对象,那就将它直接放回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  /*
  *   检查对象类型,如果类型不属于第一类或者第二类,那么就直接返回
  *   为什么要区分一类二类呢? 那么这个就直接跟数据类型的劫持方式有些关系
  *    switch (rawType) {
  *      case 'Object':
  *      case 'Array':
  *        return TargetType.COMMON
  *      case 'Map':
  *      case 'Set':
  *      case 'WeakMap':
  *      case 'WeakSet':
  *        return TargetType.COLLECTION
  *      default:
  *        return TargetType.INVALID
  *    }
  *   这里解释一下为什么要分两个类型区分拦截
  *   正常像是Obj或者arr这种类型的可以直接拦截 get set来进行操作
  *   但是Set,Map这一类的没有办法直接用Proxy拦截到add set has的方法
  *   所以换了一个方式是拦截方法获取来进行拦截操作,通过get拦截实例方法获取来进行拦截
  */
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    // 判断类型并采用对用拦截,这里由于我们配置项中配置的返回值是plain object那命中 baseHandlers
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 关键字判断 isReactive
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    // 关键字判断 readonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
    // 关键字判断是否已经被代理
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)

    // 如果是数组,并且命中重写方法范围,返回绑定上下文的执行方法
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    const res = Reflect.get(target, key, receiver)

    // 如果命中特殊不需要触发响应式的关键节点也直接返回
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    if (!isReadonly) {
      // 设置追踪时间,也就是采集当前副作用上下文
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }
    // 如果取出的值是对象,那么也要增加响应式控制
    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    // 代理taget的proxy对象
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    if (!shallow && !isReadonly(value)) {
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        // 如果值是ref
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    // 是否存在这个KEY
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // 设置对象数据
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // 触发事件,我理解也就是触发副作用上下文的事件
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()

function createArrayInstrumentations() {
  const instrumentations: Record<string, Function> = {}
  // instrument identity-sensitive Array methods to account for possible reactive
  // values
  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      const arr = toRaw(this) as any
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        return arr[key](...args.map(toRaw))
      } else {
        return res
      }
    }
  })
  // instrument length-altering mutation methods to avoid length being tracked
  // which leads to infinite loops in some cases (#2137)
  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      pauseTracking()
      const res = (toRaw(this) as any)[key].apply(this, args)
      resetTracking()
      return res
    }
  })
  return instrumentations
}

// 切换文件 reactivity/src/effect.ts
// 修改了一下文件顺序

const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined
let shouldTrack = true
const trackStack: boolean[] = []

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }

  const _effect = new ReactiveEffect(fn)
  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope
    (_effect, options.scope)
  }
  if (!options || !options.lazy) {
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}
export function stop(runner: ReactiveEffectRunner) {
  runner.effect.stop()
}

export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}

export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}

export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}
// 追踪
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 如果没有可追踪对象了那就返回
  if (!isTracking()) {
    return
  }
  // 取出当前对象的depsMap 依赖图谱
  // key是对象
  // value是一个Map
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  // 取出当前Key值的依赖Set
  // key是key
  // value是Set 值为 ReactiveEffect
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = createDep()))
  }
  /* 
  * 当前的activeEffect 是 ReactiveEffect类型
  * 其中的fn是 componentUpdateFn(...) 也就是 
  * 在patch中 setupRenderEffect(...)中声明的函数,我们在后面会分析
  */
  const eventInfo = __DEV__
    ? { effect: activeEffect, target, type, key }
    : undefined

  trackEffects(dep, eventInfo)
}
// createDep, wasTracked, newTracked 来源于 reactivity/src/dep.ts 为了方便查看我就粘贴过来了
// trackOpBit 在effect中 ReactiveEffect run时会被赋值数据
export let trackOpBit = 1
export const createDep = (effects?: ReactiveEffect[]): Dep => {
  const dep = new Set<ReactiveEffect>(effects) as Dep
  dep.w = 0
  dep.n = 0
  return dep
}
export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0
export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0
const initDepMarkers = ({ deps }) => {
    if (deps.length) {
        for (let i = 0; i < deps.length; i++) {
            deps[i].w |= trackOpBit; // set was tracked
        }
    }
};

// 回到effect.js文件
export function isTracking() {
  return shouldTrack && activeEffect !== undefined
}

export function trackEffects(
  // value是Set 值为 ReactiveEffect
  dep: Dep,
  // undefined
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  1 <= 30
  if (effectTrackDepth <= maxMarkerBits) {
    // trackOpBit 此时运行时为 2,
    if (!newTracked(dep)) {
      // 按位赋值
      // 比如 dep.n 为 0000
      // 比如 trackOpBit 为 0010
      // 结果就是为 0010 十进制转换后为 2
      dep.n |= trackOpBit // set newly tracked
      // true
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }
  // 应该追踪
  if (shouldTrack) {
    // 在dep种增加响应效应
    dep.add(activeEffect!)
    // 在当前活跃的响应效应种增加当前依赖
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        Object.assign(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo
        )
      )
    }
  }
}

// 触发, 这里为了调试数据变化带来的变动,我在页面上加了按钮修改a的数据 把 1 变成 2
export function trigger(
  // { a: 2 }
  target: object,
  // set
  type: TriggerOpTypes,
  // a
  key?: unknown,
  // 2
  newValue?: unknown,
  // 1
  oldValue?: unknown,
  // null
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }

  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      // 取出收集的Set<ReactiveEffect>推入队列
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined

  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        // 调用 triggerEffects 的方法
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  for (const effect of isArray(dep) ? dep : [...dep]) {
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      /*
      * 如果构建时存在scheduler 那么就调用 scheduler,否则就调用 run方法
      * 我们跟着当前逻辑走5-6层的函数栈后还是来到了effect.run()方法执行,于是我们看下面执行过程中发生了什么
      */
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        // 不然就遍历run方法,run中实际调度的就是 也就是实际的注册方法
        effect.run()
      }
    }
  }
}

export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []

  // can be attached after creation
  computed?: boolean
  allowRecurse?: boolean
  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope | null
  ) {
    // 绑定scope
    recordEffectScope(this, scope)
  }

  run() {
    if (!this.active) {
      return this.fn()
    }
    if (!effectStack.includes(this)) {
      try {
        // 却换当前活跃响应上下文
        effectStack.push((activeEffect = this))
        // 开始追踪
        enableTracking()

        trackOpBit = 1 << ++effectTrackDepth
        1 <= 30
        if (effectTrackDepth <= maxMarkerBits) {
          // 把Dep中的w设置为2
          initDepMarkers(this)
        } else {
          cleanupEffect(this)
        }
        // 执行具体函数
        return this.fn()
      } finally {
        1 <= 30
        if (effectTrackDepth <= maxMarkerBits) {
          // 设置Dep为 n: 0, w: 0 
          finalizeDepMarkers(this)
        }
        // 还原trackOpBit
        trackOpBit = 1 << --effectTrackDepth
        // 还原追踪数据
        resetTracking()
        // 弹出副作用栈
        effectStack.pop()
        const n = effectStack.length
        // 如果栈中还有那么调整为上一个,预计继续执行上一个站内的run方法,这个我们在后续有机会再文章
        activeEffect = n > 0 ? effectStack[n - 1] : undefined
      }
    }
  }

  stop() {
    if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

  • trackEffects的函数调用栈 image.png

思考: 那么基于我们这个Effect触发的追踪以及了解,我们回到最上方开始看mountComponent的代码处,继续去了解setupRenderEffect(...),记住此时我们的instance 已经完成了状态初始化,代理的设置,但是我们刚了解的反应式api的get过程实际上还是没有跑的,所以下文中的render过程中肯定会涉及到track的过程,那么我们就可以直接了解发生了什么事情。

runtime-core(后半程)

挂载副作用以及渲染过程(不含编译)

// runtime-core/src/renderer.ts patch 函数上下文

 const setupRenderEffect: SetupRenderEffectFn = (
    // 组件实例
    instance,
    // 组件初始化的Vnode节点
    initialVNode,
    // 组件要挂载的上下文
    container,
    // 锚(暂无头绪) 默认null
    anchor,
    // 父级悬念(暂无头绪) 默认null
    parentSuspense,
    // 是否是SVG  默认false
    isSVG,
    // 是否开启优化 默认false
    optimized
  ) => {
  
    componentUpdateFn = () => {
       // 放在下面同级方便查看
    }
    // create reactive effect for rendering
    const effect = (instance.effect = new ReactiveEffect(
      // run()
      componentUpdateFn,
      // scheduler() queueJob我个人简单理解就是做了一个任务队列,主要是通过JS的事件循环机制中的微任务来把可非阻塞任务,推入队列中,让js-core慢慢消费
      () => queueJob(instance.update),
      // 组件副作用域,这里的作用主要是吧,新构建的reactiveEffect推入effectScope中
      instance.scope // track it in component's effect scope
    ))
    // 组件更新方法实际上就是包裹effect的componentUpdateFn,这个地方上文也有涉及到过
    const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
    update.id = instance.uid
    // allowRecurse
    // #1801, #2043 component render effects should allow recursive updates
    toggleRecurse(instance, true)

    // 简化部分调试代码
    ...
    // 我们看到 componentUpdateFn
    update()
  }
  function toggleRecurse(
      { effect, update }: ComponentInternalInstance,
    allowed: boolean
  ) {
    effect.allowRecurse = update.allowRecurse = allowed
  }

思考:那么实际上这里就是为componentUpdateFn(...)方法包裹一层effect方便reactivity在track的时候可以采集到,那么我们来看一下在componentUpdateFn(...)中发生了什么

// runtime-core/src/renderer.ts setupRenderEffect 函数上下文
const componentUpdateFn = () => {
  // 挂载DOM节点标记判断
  if (!instance.isMounted) {
    let vnodeHook: VNodeHook | null | undefined
    const { el, props } = initialVNode
    const { bm, m, parent } = instance
    // 判断当前节点是否为异步组件包装
    const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
    // 设置递归为false
    toggleRecurse(instance, false)
    // beforeMount hook
    //  invokeArrayFns 为方法执行的语法糖,主要用于执行方法数组
    if (bm) {
      invokeArrayFns(bm)
    }
    // onVnodeBeforeMount
    // 执行Vnode中的bm方法
    if (
      !isAsyncWrapperVNode &&
      (vnodeHook = props && props.onVnodeBeforeMount)
    ) {
      invokeVNodeHook(vnodeHook, parent, initialVNode)
    }
    // 派发beforeMount事件
    if (
      __COMPAT__ &&
      isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
    ) {
      instance.emit('hook:beforeMount')
    }
    // 打开事件递归开关
    toggleRecurse(instance, true)

    if (el && hydrateNode) {
      // vnode has adopted host node - perform hydration instead of mount.
      const hydrateSubTree = () => {
        // 对应子树的 vnode
        instance.subTree = renderComponentRoot(instance)
        hydrateNode!(
          el as Node,
          instance.subTree,
          instance,
          parentSuspense,
          null
        )
      }

      if (isAsyncWrapperVNode) {
        ;(initialVNode.type as ComponentOptions).__asyncLoader!().then(
          // note: we are moving the render call into an async callback,
          // which means it won't track dependencies - but it's ok because
          // a server-rendered async wrapper is already in resolved state
          // and it will never need to change.
          () => !instance.isUnmounted && hydrateSubTree()
        )
      } else {
        hydrateSubTree()
      }
    } else {
      /*
      * 对应子树的 vnode
      * 同时renderComponentRoot也调用了render方法,在render的过程中,肯定也是触发track的过程
      * 数据结构贴在下方
      */
      const subTree = (instance.subTree = renderComponentRoot(instance))
      /* 
      * 对于生成的vnode节点继续patch
      * 生成对应的dom节点并且挂在到container中,那么实际上在这个步骤里面我们就回去渲染<img/>,<div/>,<HelloWorld/>
      * 这里在下方代码中我们用img的生成过程
      */ 
      patch(
        null,
        subTree,
        container,
        anchor,
        instance,
        parentSuspense,
        isSVG
      )
      initialVNode.el = subTree.el
    }
    // mounted hook 生命周期钩子
    if (m) {
      queuePostRenderEffect(m, parentSuspense)
    }
    // onVnodeMounted
    if (
      !isAsyncWrapperVNode &&
      (vnodeHook = props && props.onVnodeMounted)
    ) {
      const scopedInitialVNode = initialVNode
      queuePostRenderEffect(
        () => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
        parentSuspense
      )
    }
    if (
      __COMPAT__ &&
      isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
    ) {
      queuePostRenderEffect(
        () => instance.emit('hook:mounted'),
        parentSuspense
      )
    }

    // activated hook for keep-alive roots.
    // #1742 activated hook must be accessed after first render
    // since the hook may be injected by a child keep-alive
    if (initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
      instance.a && queuePostRenderEffect(instance.a, parentSuspense)
      if (
        __COMPAT__ &&
        isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
      ) {
        queuePostRenderEffect(
          () => instance.emit('hook:activated'),
          parentSuspense
        )
      }
    }
    // 完成挂载
    instance.isMounted = true

    // #2458: deference mount-only object parameters to prevent memleaks
    initialVNode = container = anchor = null as any
  } else {
    // updateComponent
    // This is triggered by mutation of component's own state (next: null)
    // OR parent calling processComponent (next: VNode)
    let { next, bu, u, parent, vnode } = instance
    let originNext = next
    let vnodeHook: VNodeHook | null | undefined
    
    // Disallow component effect recursion during pre-lifecycle hooks.
    toggleRecurse(instance, false)
    if (next) {
      next.el = vnode.el
      updateComponentPreRender(instance, next, optimized)
    } else {
      next = vnode
    }

    // beforeUpdate hook
    if (bu) {
      invokeArrayFns(bu)
    }
    // onVnodeBeforeUpdate
    if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
      invokeVNodeHook(vnodeHook, parent, next, vnode)
    }
    if (
      __COMPAT__ &&
      isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
    ) {
      instance.emit('hook:beforeUpdate')
    }
    toggleRecurse(instance, true)

    // render
    // 新节点
    const nextTree = renderComponentRoot(instance)
    // 老节点
    const prevTree = instance.subTree
    instance.subTree = nextTree

    patch(
      prevTree,
      nextTree,
      // parent may have changed if it's in a teleport
      hostParentNode(prevTree.el!)!,
      // anchor may have changed if it's in a fragment
      getNextHostNode(prevTree),
      instance,
      parentSuspense,
      isSVG
    )
    next.el = nextTree.el
    if (originNext === null) {
      // self-triggered update. In case of HOC, update parent component
      // vnode el. HOC is indicated by parent instance's subTree pointing
      // to child component's vnode
      updateHOCHostEl(instance, nextTree.el)
    }
    // updated hook
    if (u) {
      queuePostRenderEffect(u, parentSuspense)
    }
    // onVnodeUpdated
    if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
      queuePostRenderEffect(
        () => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
        parentSuspense
      )
    }
    if (
      __COMPAT__ &&
      isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
    ) {
      queuePostRenderEffect(
        () => instance.emit('hook:updated'),
        parentSuspense
      )
    }
  }
}
  • subTree 的数据结构 image.png

思考:那么整体来说首次挂载就结束了,但是目前我们还没有看到的两块东西分别是,patch处理element节点以及compiler函数的内部细节,我们先来了解patch处理img标签的过程吧,compiler我认为可以后续专门出一个专题来讲解

// runtime-core/src/renderer.ts patch
  const patch: PatchFn = (
    // null
    n1,
    // img 的 vnode 下方有图
    n2,
    // DOM #app
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  ) => {
    if (n1 === n2) {
      return
    }

    // patching & not same type, unmount old tree
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }

    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }

    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text:
        processText(n1, n2, container, anchor)
        break
      case Comment:
        processCommentNode(n1, n2, container, anchor)
        break
      case Static:
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      case Fragment:
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        break
      default:
        // 实际上会命中ELEMENT的渲染过程
        if (shapeFlag & ShapeFlags.ELEMENT) {
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(
            n1 as TeleportVNode,
            n2 as TeleportVNode,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          ;(type as typeof SuspenseImpl).process(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, `(${typeof type})`)
        }
    }

    // set ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
    }
  }
  const processElement = (
    // null
    n1: VNode | null,
    // img的vnode
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    isSVG = isSVG || (n2.type as string) === 'svg'
    // 我们由于是首次渲染所以命中mountElement方法
    if (n1 == null) {
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    } else {
      patchElement(
        n1,
        n2,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }

  const mountElement = (
    // img vnode
    vnode: VNode,
    // dom #app
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    let el: RendererElement
    let vnodeHook: VNodeHook | undefined | null
    const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode
    if (
      !__DEV__ &&
      vnode.el &&
      hostCloneNode !== undefined &&
      patchFlag === PatchFlags.HOISTED
    ) {
      // If a vnode has non-null el, it means it's being reused.
      // Only static vnodes can be reused, so its mounted DOM nodes should be
      // exactly the same, and we can simply do a clone here.
      // only do this in production since cloned trees cannot be HMR updated.
      el = vnode.el = hostCloneNode(vnode.el)
    } else {
      // 根据vnode创建元素节点, 这里的hostCreateElement就是在最开始我们设置option中的nodeOps, 下方贴出nodeOps相关代码
      el = vnode.el = hostCreateElement(
        // img
        vnode.type as string,
        // false
        isSVG,
        props && props.is,
        /*
        * { alt: "Vue logo",  src: "./assets/logo.png" }
        */ 
        props
      )

      // mount children first, since some props may rely on child content
      // being already rendered, e.g. `<select value>`
      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        hostSetElementText(el, vnode.children as string)
      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        mountChildren(
          vnode.children as VNodeArrayChildren,
          el,
          null,
          parentComponent,
          parentSuspense,
          isSVG && type !== 'foreignObject',
          slotScopeIds,
          optimized
        )
      }

      if (dirs) {
        invokeDirectiveHook(vnode, null, parentComponent, 'created')
      }
      // props
      if (props) {
        for (const key in props) {
          if (key !== 'value' && !isReservedProp(key)) {
          // 处理DOM元素对应的attrs
            hostPatchProp(
              el,
              key,
              null,
              props[key],
              isSVG,
              vnode.children as VNode[],
              parentComponent,
              parentSuspense,
              unmountChildren
            )
          }
        }
        /**
         * Special case for setting value on DOM elements:
         * - it can be order-sensitive (e.g. should be set *after* min/max, #2325, #4024)
         * - it needs to be forced (#1471)
         * #2353 proposes adding another renderer option to configure this, but
         * the properties affects are so finite it is worth special casing it
         * here to reduce the complexity. (Special casing it also should not
         * affect non-DOM renderers)
         */
        if ('value' in props) {
          hostPatchProp(el, 'value', null, props.value)
        }
        if ((vnodeHook = props.onVnodeBeforeMount)) {
          invokeVNodeHook(vnodeHook, parentComponent, vnode)
        }
      }
      // scopeId
      setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent)
    }
    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
      Object.defineProperty(el, '__vnode', {
        value: vnode,
        enumerable: false
      })
      Object.defineProperty(el, '__vueParentComponent', {
        value: parentComponent,
        enumerable: false
      })
    }
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
    }
    // #1583 For inside suspense + suspense not resolved case, enter hook should call when suspense resolved
    // #1689 For inside suspense + suspense resolved case, just call it
    const needCallTransitionHooks =
      (!parentSuspense || (parentSuspense && !parentSuspense.pendingBranch)) &&
      transition &&
      !transition.persisted
    if (needCallTransitionHooks) {
      transition!.beforeEnter(el)
    }
    // 将渲染后的节点插入对应的上下文也就#app中,如果跟我一起debug的小伙伴实际上就回发现这里已经可以在 chrome debug的 Elements中已经有了对应的树节点了
    hostInsert(el, container, anchor)
    if (
      (vnodeHook = props && props.onVnodeMounted) ||
      needCallTransitionHooks ||
      dirs
    ) {
      queuePostRenderEffect(() => {
        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
        needCallTransitionHooks && transition!.enter(el)
        dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
      }, parentSuspense)
    }
  }
  
  // 切换文件到runtime-dom/src/nodeOps.ts
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
  // 在指定的子节点前添加相关节点的方法
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
  },
  // 从指定的父列表中删除一个子节点
  remove: child => {
    const parent = child.parentNode
    if (parent) {
      parent.removeChild(child)
    }
  },
  // 创建指定的DOM节点
  createElement: (tag, isSVG, is, props): Element => {
    const el = isSVG
      ? doc.createElementNS(svgNS, tag)
      : doc.createElement(tag, is ? { is } : undefined)

    if (tag === 'select' && props && props.multiple != null) {
      ;(el as HTMLSelectElement).setAttribute('multiple', props.multiple)
    }

    return el
  },
  // 创建文本节点
  createText: text => doc.createTextNode(text),
  // 创建注释节点
  createComment: text => doc.createComment(text),
  // 设置节点中的值 一般 适用于设置文本节点跟注释节点
  setText: (node, text) => {
    node.nodeValue = text
  },
  // 设置节点中的文本内容(这里感觉与setText方法上有些雷同,但是设置的对象不同,textContent主要针对一般的DOM对象)
  setElementText: (el, text) => {
    el.textContent = text
  },
  // 返回父节点
  parentNode: node => node.parentNode as Element | null,
  // 返回同层级的下一个兄弟节点
  nextSibling: node => node.nextSibling,
  // 查询
  querySelector: selector => doc.querySelector(selector),
  // 设置scope-id到节点的attrs上
  setScopeId(el, id) {
    el.setAttribute(id, '')
  },
  // 拷贝DOM树
  cloneNode(el) {
    const cloned = el.cloneNode(true)
    // #3072
    // - in `patchDOMProp`, we store the actual value in the `el._value` property.
    // - normally, elements using `:value` bindings will not be hoisted, but if
    //   the bound value is a constant, e.g. `:value="true"` - they do get
    //   hoisted.
    // - in production, hoisted nodes are cloned when subsequent inserts, but
    //   cloneNode() does not copy the custom property we attached.
    // - This may need to account for other custom DOM properties we attach to
    //   elements in addition to `_value` in the future.
    if (`_value` in el) {
      ;(cloned as any)._value = (el as any)._value
    }
    return cloned
  },

  // __UNSAFE__
  // Reason: innerHTML.
  // Static content here can only come from compiled templates.
  // As long as the user only uses trusted templates, this is safe.
  insertStaticContent(content, parent, anchor, isSVG) {
    // <parent> before | first ... last | anchor </parent>
    const before = anchor ? anchor.previousSibling : parent.lastChild
    let template = staticTemplateCache.get(content)
    if (!template) {
      const t = doc.createElement('template')
      t.innerHTML = isSVG ? `<svg>${content}</svg>` : content
      template = t.content
      if (isSVG) {
        // remove outer svg wrapper
        const wrapper = template.firstChild!
        while (wrapper.firstChild) {
          template.appendChild(wrapper.firstChild)
        }
        template.removeChild(wrapper)
      }
      staticTemplateCache.set(content, template)
    }
    parent.insertBefore(template.cloneNode(true), anchor)
    return [
      // first
      before ? before.nextSibling! : parent.firstChild!,
      // last
      anchor ? anchor.previousSibling! : parent.lastChild!
    ]
  }
}

  • img的vnode节点 image.png

总结: 那么就此我们大致了解了整体的构建过程,当然留下了下方的TODO,后续作者尽快补充

  • complier 函数内部的运行以及实现
  • reactivity的多层副作用逻辑以及scoped的作用

当然vue3.*源码中还有更多的细节需要大家一起去看去讨论,我这里只是记录自己的一些愚见,有问题也希望大家看完后给我留言