# 渲染到页面流程介绍

# 一个简单的示例说明下流程

const RootComponent = {
  template: `
    <div>test</div>
    <p :class="state.name">
      Count is
    </p>
    <div v-for="value in state.fordata">
        {{value}}
    </div>
    <button @click="increment" v-if="state.show">
      Count is: {{ state.count }}
    </button>
  `,
  setup() {
    const state = Vue.reactive({
      name:'test',
      count: 2,
      show: true,
      fordata: [222, 333, 4444]
    })
    function increment() {
      state.count++
    }

    return {
      state,
      increment
    }
  }
}
Vue.createApp().mount(RootComponent, '#container')

根据这段代码示例我们看下流程,由上面分析可以,先触发render函数就是触发里面的补丁path函数,我们可 以简单看下这个函数的,主要根据vnode节点类型来加工不同的vnode

function patch(
    n1: HostVNode | null, // 如果为空就是要装载这个组件
    n2: HostVNode,
    container: HostElement,
    anchor: HostNode | null = null,
    parentComponent: ComponentInternalInstance | null = null,
    parentSuspense: HostSuspenseBoundary | null = null,
    isSVG: boolean = false,
    optimized: boolean = false
  ) {
    // patching & not same type, unmount old tree
    if (n1 != null) {
      if (!isSameType(n1, n2)) {
        anchor = getNextHostNode(n1)
        unmount(n1, parentComponent, parentSuspense, true)
        n1 = null
      } else if (n1.props && n1.props.$once) {
        return
      }
    }
    const { type, shapeFlag } = n2
    switch (type) {
      case Text:
        processText(n1, n2, container, anchor)
        break
      case Comment:
        processCommentNode(n1, n2, container, anchor)
        break
      case Fragment:
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
        break
      case Portal:
        processPortal(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
        break
      case Suspense:
        if (__FEATURE_SUSPENSE__) {
          processSuspense(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
        } else if (__DEV__) {
          warn(`Suspense is not enabled in the version of Vue you are using.`)
        }
        break
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
        } else if (__DEV__) {
          warn('Invalid HostVNode type:', n2.type, `(${typeof n2.type})`)
        }
    }
  }

由上面可知我们要加工组件vnode所以就使用此方法processComponent,这个方法包含了组件载入组件跟新等 一系列和组件有关方法,这里看逻辑可知触发里面的mountComponent方法

// 简化之后的大概方法如下
function mountComponent(
  initialVNode: HostVNode,
  container: HostElement,
  anchor: HostNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: HostSuspenseBoundary | null,
  isSVG: boolean
) {
  // component.ts中createComponentInstance方法生成组件实例并挂载到vnode.component
  // 组件内部实例的vnode对象也会记住组件初始化实例
  // 里面还包含很多其他为空的键如subTree已经生命周期等定义虽然都为空
  const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
    initialVNode,
    parentComponent
  ))
  // 处理props and slots for setup context
  const propsOptions = (initialVNode.type as Component).props
  resolveProps(instance, initialVNode.props, propsOptions)
  resolveSlots(instance, initialVNode.children)

  // 装载组件的状态逻辑
  if (initialVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
    setupStatefulComponent(instance, parentSuspense)
  }
  // 设置组件的渲染回调effect函数
  setupRenderEffect(
    instance,
    parentSuspense,
    initialVNode,
    container,
    anchor,
    isSVG
  )
}

接着上面分析下component.ts中setupStatefulComponent方法

export function setupStatefulComponent(
  instance: ComponentInternalInstance,
  parentSuspense: SuspenseBoundary | null
) {
  const Component = instance.type as ComponentOptions
  // 检查组件命名
  if (__DEV__) {
    if (Component.name) {
      validateComponentName(Component.name, instance.appContext.config)
    }
    if (Component.components) {
      const names = Object.keys(Component.components)
      for (let i = 0; i < names.length; i++) {
        const name = names[i]
        validateComponentName(name, instance.appContext.config)
      }
    }
  }
  // 创建render的代理,最终在mount之后就是返回的这个
  // PublicInstanceProxyHandlers位于componentProxy中,是构造整个组件内部实例的代理,其值绑定在内
  // 部实例的renderProxy属性上
  instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers)
  // 2. 创建props的代理,setup函数第一个参数就是他
  const propsProxy = (instance.propsProxy = readonly(instance.props))
  // 这个setup就是组件里面的setup函数
  const { setup } = Component
  if (setup) {
    // setup函数接受一个以上参数的话,第二个参数就是其运行时候上下文,需要生成
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)
    currentInstance = instance
    currentSuspense = parentSuspense
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [propsProxy, setupContext]  //运行setup时候函数参数
    )
    currentInstance = null
    currentSuspense = null
    if (
      setupResult &&
      isFunction(setupResult.then) &&
      isFunction(setupResult.catch)
    ) {
      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.`
        )
      }
      return
    } else {
      // 如果返回值是函数则绑定到实例的render上,如果是对象则reactive化并绑定到renderContext上
      // 之后还是触发finishComponentSetup方法
      handleSetupResult(instance, setupResult, parentSuspense)
    }
  } else {
    // 该方法主要构造组件实例的render方法(如果不存在的话)也会绑定到实例type.render上,使用的是
    // compile方法传入模版生成渲染函数(如果组件初始化时候有了render函数话就直接赋值给实例render函数)
    finishComponentSetup(instance, parentSuspense)
  }
}
// 构造整个组件内部实例的代理,其值绑定在内部实例的renderProxy属性上mount之后也是返回的该值
// 当运行编译后的render函数的时候,其传入的上下文就是renderProxy,此时在里面取值的话就会触发
// 此get方法, 从而取到值
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
  get(target: ComponentInternalInstance, key: string) {
    const { renderContext, data, props, propsProxy } = target
    // renderContext组件内部setup函数运行之后返回值的reactive化的返回值即reactive(setup())
    // 还有inject打入的值也绑定在renderContext上
    // data实际上是为了支持2.0的写法,如果没有setup方法,但是有data方法的话,就reactive(data()),并绑定到实例的data上
    if (data !== EMPTY_OBJ && hasOwn(data, key)) {
      return data[key]
    } else if (hasOwn(renderContext, key)) {
      return renderContext[key]
    } else if (hasOwn(props, key)) {
      // return the value from propsProxy for ref unwrapping and readonly
      return propsProxy![key]
    } else if (key === '$el') {
      return target.vnode.el
    } else if (hasOwn(publicPropertiesMap, key)) {
      return target[publicPropertiesMap[key]]
    }
    // methods are only exposed when options are supported
    if (__FEATURE_OPTIONS__) {
      switch (key) {
        case '$forceUpdate':
          return target.update
        case '$nextTick':
          return nextTick
        case '$watch':
          return instanceWatch.bind(target)
      }
    }
    return target.user[key]
  },

  set(target: ComponentInternalInstance, key: string, value: any): boolean {
    const { data, renderContext } = target
    if (data !== EMPTY_OBJ && hasOwn(data, key)) {
      data[key] = value
    } else if (hasOwn(renderContext, key)) {
      renderContext[key] = value
    } else if (key[0] === '$' && key.slice(1) in target) {
      __DEV__ &&
        warn(
          `Attempting to mutate public property "${key}". ` +
            `Properties starting with $ are reserved and readonly.`,
          target
        )
      return false
    } else if (key in target.props) {
      __DEV__ &&
        warn(`Attempting to mutate prop "${key}". Props are readonly.`, target)
      return false
    } else {
      target.user[key] = value
    }
    return true
  }
}

最后触发setupRenderEffect方法

// 该方法运行数据响应系统effect方法定义并运行数据响应时候对应的回调函数,
function setupRenderEffect(
    instance: ComponentInternalInstance,
    parentSuspense: HostSuspenseBoundary | null,
    initialVNode: HostVNode,
    container: HostElement,
    anchor: HostNode | null,
    isSVG: boolean
  ) {
  // 创建reactive effect用来组件渲染并绑定到实例update上面
  let mounted = false
  instance.update = effect(function componentEffect() {
    if (!mounted) {
      // 使用component.ts中renderComponentRoot方法生成模版字符串
      //(实际上就是运行实例的render函数)并绑定到实例的subTree方法上
      // 此render函数运行时候会传入上下文,看代码可知是renderProxy,也就是整个组件内部实例的proxy
      const subTree = (instance.subTree = renderComponentRoot(instance))
      // beforeMount钩子
      if (instance.bm !== null) {
        invokeHooks(instance.bm)
      }
      //传入上面的subTree之后运行patch --> processElement --> mountElement该方法里面就是
      // 创建节点并插入节点绑定响应点击事件
      patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
      initialVNode.el = subTree.el
      // mounted钩子
      if (instance.m !== null) {
        queuePostRenderEffect(instance.m, parentSuspense)
      }
      mounted = true
    } else {
      // 组件更新时候触发
      // 组件内部状态改变时候触发此时(next: null),例如上面的点击事件触发时候会进入该方法
      // 或者父组件调用processComponent时候触发此时(next: HostVNode)
      const { next } = instance

      if (__DEV__) {
        pushWarningContext(next || instance.vnode)
      }

      if (next !== null) {
        // 更新实例的vnode和next置为null等
        updateComponentPreRender(instance, next)
      }
      const prevTree = instance.subTree
      // 重新运行render函数生成字符串
      const nextTree = (instance.subTree = renderComponentRoot(instance))
      // beforeUpdate钩子
      if (instance.bu !== null) {
        invokeHooks(instance.bu)
      }
      // reset refs
      // only needed if previous patch had refs
      if (instance.refs !== EMPTY_OBJ) {
        instance.refs = {}
      }
      // 这里触发更新载入, 我们的点击事件例子就会按这个流程触发载入
      // patch --> processElement --> patchElement --> hostSetElementText
      patch(
        prevTree,
        nextTree,
        // parent may have changed if it's in a portal
        hostParentNode(prevTree.el as HostNode) as HostElement,
        // anchor may have changed if it's in a fragment
        getNextHostNode(prevTree),
        instance,
        parentSuspense,
        isSVG
      )
      instance.vnode.el = nextTree.el
      if (next === null) {
        // hoc就是高阶组件,是一个function函数,可以看出它不会修改原来的组件,使用
        // return去返回一个组件然后再渲染被包装的组件
        // 高阶组件只接受数据props,不关心数据来源。等其他特点
        updateHOCHostEl(instance, nextTree.el)
      }
      // updated hook
      if (instance.u !== null) {
        queuePostRenderEffect(instance.u, parentSuspense)
      }

      if (__DEV__) {
        popWarningContext()
      }
    }
  }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}

# 计算属性、渲染函数、数据监听等流程

const { render, createComponent, h } = Vue;
const Comp = createComponent({
  data() {
    return {
      foo: 1
    }
  },
  extends: {
    data() {
      return {
        my: 1
      }
    }
  },
  watch: {
    foo: function(val, oldval) {
      console.log(val);
    }
  },
  computed: {
    bar() {
      return this.foo + 1
    },
    baz() {
      return this.bar + 1
    }
  },
  render() {
    return h(
      'div',
      {
        onClick: () => {
          this.foo++
          console.log(this.my)
        }
      },
      this.bar + this.baz
    )
  }
})
const vnode = h(Comp);
render(vnode, '#container');

h函数就是用来生成vnode的,render函数用来渲染,这里的方式和第一个流程一样,不同在component.ts中 finishComponentSetup中渲染函数是用组件里面的函数赋值的,不是compile编译出来,还有就是调用 apiOptions.ts中applyOptions方法来处理组件,在里面处理组件所有参数,可以看下该函数

export function applyOptions(
  instance: ComponentInternalInstance,
  options: ComponentOptions,
  asMixin: boolean = false
) {
  const renderContext =
    instance.renderContext === EMPTY_OBJ
      ? (instance.renderContext = reactive({}))
      : instance.renderContext
  const ctx = instance.renderProxy!
  const {
    // composition
    mixins,
    extends: extendsOptions,
    // state
    data: dataOptions,
    computed: computedOptions,
    methods,
    watch: watchOptions,
    provide: provideOptions,
    inject: injectOptions,
    // assets
    components,
    directives,
    // lifecycle
    beforeMount,
    mounted,
    beforeUpdate,
    updated,
    // TODO activated
    // TODO deactivated
    beforeUnmount,
    unmounted,
    renderTracked,
    renderTriggered,
    errorCaptured
  } = options

  const globalMixins = instance.appContext.mixins
  // applyOptions is called non-as-mixin once per instance
  if (!asMixin) {
    callSyncHook('beforeCreate', options, ctx, globalMixins)
    // 全局mixin一个实例上只需要载入一次
    applyMixins(instance, globalMixins)
  }
  // extends参数存储基础组件component
  // extends: {
  //   data() {
  //     return {
  //       a: 1
  //     }
  //   },
  //   mounted() {
  //     calls.push('base')
  //   }
  // }
  if (extendsOptions) {
    // 作为基础组件不需要再次载入全局mixin
    applyOptions(instance, extendsOptions, true)
  }
  // 本地mixins参数存在
  if (mixins) {
    applyMixins(instance, mixins)
  }
  // 初始化传入里面有data函数
  if (dataOptions) {
    const data = isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions
    if (!isObject(data)) {
      __DEV__ && warn(`data() should return an object.`)
    } else if (instance.data === EMPTY_OBJ) {
      // 函数返回值进行reactive化,并绑定到实例的data上面
      instance.data = reactive(data)
    } else {
      // 如果该组件中有mixins和extends基础组件参数那么data值必然已经被赋值了
      extend(instance.data, data)
    }
  }
  //  处理计算属性绑定到renderContext
  if (computedOptions) {
    for (const key in computedOptions) {
      const opt = (computedOptions as ComputedOptions)[key]

      if (isFunction(opt)) {
        renderContext[key] = computed(opt.bind(ctx))
      } else {
        // 不是函数支持自定义get set
        const { get, set } = opt
        if (isFunction(get)) {
          renderContext[key] = computed({
            get: get.bind(ctx),
            set: isFunction(set)
              ? set.bind(ctx)
              : __DEV__
                ? () => {
                    warn(
                      `Computed property "${key}" was assigned to but it has no setter.`
                    )
                  }
                : NOOP
          })
        } else if (__DEV__) {
          warn(`Computed property "${key}" has no getter.`)
        }
      }
    }
  }
  // 处理组件内部方法也绑定到renderContext
  if (methods) {
    for (const key in methods) {
      renderContext[key] = (methods as MethodOptions)[key].bind(ctx)
    }
  }
  // 监听参数watch大概以下几种格式
  // watch: {
  //   watchvalue: 'inMethodWay'  //methond里面的方法名字
  //   watchvalue: function(val, oldval) { }
  //   watchvalue: {
  //     handler: function(val, oldval) {
  //       console.log(val.name)
  //     },
  //     deep: true
  //   }
  // }
  // 监听函数的使用过程,调用apiWatch.ts中watch方法,之后调用里面的doWatch
  if (watchOptions) {
    for (const key in watchOptions) {
      const raw = watchOptions[key]
      const getter = () => ctx[key]
      if (isString(raw)) {
        const handler = renderContext[raw]
        if (isFunction(handler)) {
          watch(getter, handler as WatchHandler)
        } else if (__DEV__) {
          warn(`Invalid watch handler specified by key "${raw}"`, handler)
        }
      } else if (isFunction(raw)) {
        watch(getter, raw.bind(ctx))
      } else if (isObject(raw)) {
        // TODO 2.x compat
        watch(getter, raw.handler.bind(ctx), raw)
      } else if (__DEV__) {
        warn(`Invalid watch option: "${key}"`)
      }
    }
  }
  // 值都绑定到实例的provides上面
  if (provideOptions) {
    const provides = isFunction(provideOptions)
      ? provideOptions.call(ctx)
      : provideOptions
    for (const key in provides) {
      provide(key, provides[key])
    }
  }
  // 需要inject的值提最终都绑定到renderContext上面
  if (injectOptions) {
    if (isArray(injectOptions)) {
      for (let i = 0; i < injectOptions.length; i++) {
        const key = injectOptions[i]
        renderContext[key] = inject(key)
      }
    } else {
      for (const key in injectOptions) {
        const opt = injectOptions[key]
        if (isObject(opt)) {
          renderContext[key] = inject(opt.from, opt.default)
        } else {
          renderContext[key] = inject(opt)
        }
      }
    }
  }
  // asset options
  if (components) {
    extend(instance.components, components)
  }
  if (directives) {
    extend(instance.directives, directives)
  }

  // 生命周期函数
  if (!asMixin) {
    callSyncHook('created', options, ctx, globalMixins)
  }
  if (beforeMount) {
    onBeforeMount(beforeMount.bind(ctx))
  }
  if (mounted) {
    onMounted(mounted.bind(ctx))
  }
  if (beforeUpdate) {
    onBeforeUpdate(beforeUpdate.bind(ctx))
  }
  if (updated) {
    onUpdated(updated.bind(ctx))
  }
  if (errorCaptured) {
    onErrorCaptured(errorCaptured.bind(ctx))
  }
  if (renderTracked) {
    onRenderTracked(renderTracked.bind(ctx))
  }
  if (renderTriggered) {
    onRenderTriggered(renderTriggered.bind(ctx))
  }
  if (beforeUnmount) {
    onBeforeUnmount(beforeUnmount.bind(ctx))
  }
  if (unmounted) {
    onUnmounted(unmounted.bind(ctx))
  }
}

接下来我们看下dowatch方法,这个方法主要用来触发数据响应系统的依赖收集等

function doWatch(
  source: WatcherSource | WatcherSource[] | SimpleEffect,
  cb:
    | ((newValue: any, oldValue: any, onCleanup: CleanupRegistrator) => any)
    | null,
  { lazy, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): StopHandle {
  const instance = currentInstance
  const suspense = currentSuspense
  let getter: () => any
  if (isArray(source)) {
    getter = () =>
      source.map(
        s =>
          isRef(s)
            ? s.value
            : callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
      )
  } else if (isRef(source)) {
    getter = () => source.value
  } else if (cb) {
    // 运行此函数可以取到当前监听的键对应值
    getter = () =>
      callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
  } else {
    // no cb -> simple effect
    getter = () => {
      if (instance && instance.isUnmounted) {
        return
      }
      if (cleanup) {
        cleanup()
      }
      return callWithErrorHandling(
        source,
        instance,
        ErrorCodes.WATCH_CALLBACK,
        [registerCleanup]
      )
    }
  }

  if (deep) {
    const baseGetter = getter
    getter = () => traverse(baseGetter())
  }

  let cleanup: Function
  const registerCleanup: CleanupRegistrator = (fn: () => void) => {
    // TODO wrap the cleanup fn for error handling
    cleanup = runner.onStop = () => {
      callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
    }
  }
  let oldValue = isArray(source) ? [] : undefined
  // 定义响应的回调函数
  const applyCb = cb
    ? () => {
        if (instance && instance.isUnmounted) {
          return
        }
        const newValue = runner()
        if (deep || newValue !== oldValue) {
          // cleanup before running cb again
          if (cleanup) {
            cleanup()
          }
          callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
            newValue,
            oldValue,
            registerCleanup
          ])
          oldValue = newValue
        }
      }
    : void 0
  let scheduler: (job: () => any) => void
  if (flush === 'sync') {
    scheduler = invoke
  } else if (flush === 'pre') {
    scheduler = job => {
      if (!instance || instance.vnode.el != null) {
        queueJob(job)
      } else {
        // with 'pre' option, the first call must happen before
        // the component is mounted so it is called synchronously.
        job()
      }
    }
  } else {
    scheduler = job => {
      // createRenderer.ts中,该方法内部到scheduler.ts中queuePostFlushCb方法(把所有
      // 回调都写入postFlushCb数组)在里面使用nextTick以promise.then的形式触发
      // flushJobs-- > flushPostFlushCbs在该方法里面触发所有回调,这里的回调就是dowatch方法中的
      // applyCb变量,该回调的触发方式采用统一的方法callWithAsyncErrorHandling
      queuePostRenderEffect(job, suspense)
    }
  }
  const runner = effect(getter, {
    lazy: true,
    // so it runs before component update effects in pre flush mode
    computed: true,
    onTrack,
    onTrigger,
    scheduler: applyCb ? () => scheduler(applyCb) : scheduler
  })

  if (!lazy) {
    if (applyCb) {
      scheduler(applyCb)
    } else {
      scheduler(runner)
    }
  } else {
    oldValue = runner()
  }
  // 收集所有effect
  recordEffect(runner)
  return () => {
    stop(runner)
  }
}