Vue3源码解析之 render component(五)

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

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

前言

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

案例

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

html 复制代码
<!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 方法:

ts 复制代码
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 方法执行:

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

ts 复制代码
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 进行组件的挂载,此时页面呈现:

总结

  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(五)
相关推荐
大家的林语冰1 分钟前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js
jiayong2318 分钟前
第 8 课:开始引入组合式函数
前端·javascript·学习
田八22 分钟前
聊聊AI的发展史,AI的爆发并不是偶然
前端·人工智能·程序员
zhanghongbin0132 分钟前
AI 采集器:Claude Code、OpenAI、LiteLLM 监控
java·前端·人工智能
IT_陈寒38 分钟前
Python的列表推导式里藏了个坑,差点让我加班到凌晨
前端·人工智能·后端
吴声子夜歌1 小时前
ES6——正则的扩展详解
前端·mysql·es6
天***88521 小时前
Edge 浏览器离线绿色增强版+官方安装包,支持win7等系统
前端·edge
漫游的渔夫1 小时前
别再直接 `json.loads` 了!AI 返回的 JSON 坑位指南
前端·人工智能
软件工程师文艺2 小时前
从0到1:Claude Code如何用React构建CLI应用
前端·react.js·前端框架
M ? A2 小时前
Vue 迁移 React 实战:VuReact 一键自动化转换方案
前端·vue.js·经验分享·react.js·开源·自动化·vureact