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(五)
相关推荐
绝美焦栖6 分钟前
vue复杂数据类型多层嵌套的监听
前端·javascript·vue.js
GoodStudyAndDayDayUp11 分钟前
gitlab+portainer 实现Ruoyi Vue后端CI/CD
vue.js·ci/cd·gitlab
xixixin_1 小时前
【Vite】前端开发服务器的配置
服务器·前端·网络
.生产的驴1 小时前
Vue3 加快页面加载速度 使用CDN外部库的加载 提升页面打开速度 服务器分发
运维·服务器·前端·vue.js·分布式·前端框架·vue
史迪仔01121 小时前
Python生成器:高效处理大数据的秘密武器
前端·数据库·python
蓝婷儿2 小时前
前端面试每日三题 - Day 34
前端·面试·职场和发展
CopyLower2 小时前
苹果计划将AI搜索集成至Safari:谷歌搜索下降引发的市场变革
前端·人工智能·safari
郝开3 小时前
扩展:React 项目执行 yarn eject 后的 package.json 变化详解及参数解析
react.js·前端框架·react
我是Superman丶4 小时前
【技巧】前端VUE用中文方法名调用没效果的问题
前端·javascript·vue.js
斯~内克4 小时前
Vue 3 中 watch 的使用与深入理解
前端·javascript·vue.js