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(五)
相关推荐
wmm_会飞的@鱼4 分钟前
FlexSim-汽车零部件仓库布局优化与仿真
服务器·前端·网络·数据库·数学建模·汽车
yvvvy6 分钟前
从“按钮都不会点”到“能撸大厂 UI”:我用 react-vant 踢开组件库的大门!
前端·javascript
安然dn7 分钟前
Cropper.js:JS图像裁剪库
前端·javascript
Serendipity2619 分钟前
微服务架构
前端·微服务
Hilaku24 分钟前
深入background-image:你可能不知道的几个性能优化与高级技巧
前端·css
南岸月明27 分钟前
副业自媒体1年终于明白:为什么会表达的人,能量越来越强,更能赚到钱?
前端
Danny_FD40 分钟前
Vue + Element UI 实现模糊搜索自动补全
前端·javascript
gnip1 小时前
闭包实现一个简单Vue3的状态管理
前端·javascript
斐济岛上有一只斐济1 小时前
后端程序员的CSS复习
前端
Enddme1 小时前
《面试必问!JavaScript 中this 全方位避坑指南 (含高频题解析)》
前端·javascript·面试