Vue3源码解析之 render component(五)

946 阅读3分钟

本文为原创文章,未获授权禁止转载,侵权必究!

本篇是 Vue3 源码解析系列第 16 篇,关注专栏

前言

组件 的渲染除了之前提到的情况外, 在 Composition Api 中还可以通过 setup 方法来进行,那它是如何运行的呢?我们来逐一分析。

案例

首先引入 h 、 renderreactive 函数,声明一个 component 包含 setup 函数的组件对象,通过 h 函数生成 组件 vnode 对象,最后通过 render 函数渲染该对象。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../../../dist/vue.global.js"></script>
  </head>

  <body>
    <div id="app"></div>
    <script>
      const { h, render, reactive } = Vue

      const component = {
        setup() {
          const obj = reactive({
            name: 'Jason Chen'
          })

          return () => h('div', obj.name)
        }
      }

      const vnode = h(component)

      render(vnode, document.querySelector('#app'))
    </script>
  </body>
</html>

render component setup

我们知道组件的挂载通过 mountComponent 方法,之后执行 setupComponent 触发 setupStatefulComponent 方法:

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions

  // 省略
  
  const { setup } = Component
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    setCurrentInstance(instance)
    pauseTracking()
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    unsetCurrentInstance()

    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
        if (__DEV__ && !instance.suspense) {
          const name = Component.name ?? 'Anonymous'
          warn(
            `Component <${name}>: setup function returned a promise, but no ` +
              `<Suspense> boundary was found in the parent component tree. ` +
              `A component with async setup() must be nested in a <Suspense> ` +
              `in order to be rendered.`
          )
        }
      } else if (__DEV__) {
        warn(
          `setup() returned a Promise, but the version of Vue you are using ` +
            `does not support it yet.`
        )
      }
    } else {
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}

通过 组件对象 解构出 setup 函数,之后执行 createSetupContext 方法来创建上下文,我们知道 setup 函数第二个参数为 context 执行上下文

接着执行 callWithErrorHandling 方法即传入的 setup 方法执行:

render-setup.png

此时 setupResultsetup 函数返回值 () => h('div', obj.name),之后执行 handleSetupResult 方法:

export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
  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
    }
  } else if (isObject(setupResult)) {
     // 省略
  } else if (__DEV__ && setupResult !== undefined) {
    // 省略
  }
  finishComponentSetup(instance, isSSR)
}

setupResult 赋值给 instance.renderinstance.render = () => h('div', obj.name)handleSetupResult 方法执行完成就表示 组件实例 就存在 render 函数。

之后执行 setupRenderEffect 方法,触发 patch 进行组件的挂载,此时页面呈现:

render-setup-patch.png

总结

  1. 组件 本质上就是通过 render 函数实现的。
  2. 组件内部构建了一个 ReactiveEffect 实例,它是实现组件内部响应式渲染的核心。
  3. Options Api 中存在 this,所以必须要改变 this 指向来访问对应的数据,内部通过 bind 或者 call 来进行改变。
  4. Composition Api 不存在 this,也就不存在 this 指向问题,逻辑更加简单。

Vue3 源码实现

vue-next-mini

Vue3 源码解析系列

  1. Vue3源码解析之 源码调试
  2. Vue3源码解析之 reactive
  3. Vue3源码解析之 ref
  4. Vue3源码解析之 computed
  5. Vue3源码解析之 watch
  6. Vue3源码解析之 runtime
  7. Vue3源码解析之 h
  8. Vue3源码解析之 render(一)
  9. Vue3源码解析之 render(二)
  10. Vue3源码解析之 render(三)
  11. Vue3源码解析之 render(四)
  12. Vue3源码解析之 render component(一)
  13. Vue3源码解析之 render component(二)
  14. Vue3源码解析之 render component(三)
  15. Vue3源码解析之 render component(四)
  16. Vue3源码解析之 render component(五)
  17. Vue3源码解析之 diff(一)
  18. Vue3源码解析之 diff(二)
  19. Vue3源码解析之 compiler(一)
  20. Vue3源码解析之 compiler(二)
  21. Vue3源码解析之 compiler(三)
  22. Vue3源码解析之 createApp