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(五)
相关推荐
sunbyte2 小时前
Tailwind CSS 初学者入门指南:项目集成,主要变更内容!
前端·css
可爱的秋秋啊3 小时前
vue3,element ui框架中为el-table表格实现自动滚动,并实现表头汇总数据
前端·vue.js·笔记·elementui
一夜枫林3 小时前
uniapp自定义拖拽排列
前端·javascript·uni-app
IT瘾君5 小时前
JavaWeb:Html&Css
前端·html
264玫瑰资源库5 小时前
问道数码兽 怀旧剧情回合手游源码搭建教程(反查重优化版)
java·开发语言·前端·游戏
喝拿铁写前端6 小时前
从圣经Babel到现代编译器:没开玩笑,普通程序员也能写出自己的编译器!
前端·架构·前端框架
HED6 小时前
VUE项目发版后用户访问的仍然是旧页面?原因和解决方案都在这啦!
前端·vue.js
拉不动的猪6 小时前
前端自做埋点,我们应该要注意的几个问题
前端·javascript·面试
王景程6 小时前
如何测试短信接口
java·服务器·前端
安冬的码畜日常7 小时前
【AI 加持下的 Python 编程实战 2_10】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(中)
开发语言·前端·人工智能·ai·扫雷游戏·ai辅助编程·辅助编程