2026.01.21

Setup 渲染函数返回 -> Render Function 的设计与实现

在前面的章节中,我们已经实现了组件实例代理系统和属性访问拦截。在 Vue 中,除了通过 render 选项提供渲染函数外,setup 函数也可以直接返回一个渲染函数。

在前面的章节中,我们已经实现了组件实例代理系统和属性访问拦截。在 Vue 中,除了通过 render 选项提供渲染函数外,setup 函数也可以直接返回一个渲染函数。

问题场景

让我们先看一个通过 setup 返回渲染函数的典型场景:

<div id="app"></div>
<script type="module">
    import { h, createApp, ref } from '../dist/vue.esm.js'

    const Comp = {
        props: {
            msg: String
        },
        setup(props, { attrs }) {
            const count = ref(0)
            // setup 直接返回一个渲染函数
            return () => {
                return h('div', count.value)
            }
        }
        // 不再需要 render 选项
        // render() {
        //   return h('div', this.msg)
        // }
    }

    createApp(Comp, { msg: 'msg', count: 0, a: 1 }).mount('#app')
</script>

核心问题:

Vue 如何区分 setup 返回的是渲染函数还是状态对象?

区分逻辑:

  • 返回函数:认定为渲染函数,绑定到 instance.render
  • 返回对象:认定为状态对象,使用 proxyRefs 包装后绑定到 instance.setupState

这种设计让组件的定义更加灵活:

  1. 如果只需要返回渲染函数,可以直接在 setup 中返回
  2. 如果需要返回多个响应式状态,可以返回对象

实现 handleSetupResult

Setup 返回值处理逻辑

我们需要在组件初始化时判断 setup 的返回值类型,并做相应的处理:

/**
 * 处理 setup 函数的返回值
 * @param instance - 组件实例
 * @param setupResult - setup 函数的返回值
 */
function handleSetupResult(instance, setupResult) {
  /**
   * 1. 如果 setup 返回了函数,认定为是 render 函数
   */
  if (isFunction(setupResult)) {
    instance.render = setupResult
  }

  /**
   * 2. 如果返回了对象,认定为是状态对象
   */
  else if (isObject(setupResult)) {
    instance.setupState = proxyRefs(setupResult)
  }
}

核心设计:

  • 使用 isFunction 判断是否为渲染函数
  • 使用 isObject 判断是否为状态对象
  • 渲染函数直接赋值到 instance.render
  • 状态对象使用 proxyRefs 包装后赋值到 instance.setupState

更新 setupStatefulComponent

现在我们需要更新 setupStatefulComponent 函数,调用 handleSetupResult 处理 setup 的返回值:

/**
 * 初始化有状态组件
 */
function setupStatefulComponent(instance) {
  const { type } = instance

  /**
   * 创建代理对象,绑定到 instance.proxy
   */
  instance.proxy = new Proxy(instance.ctx, publicInstanceProxyHandlers)

  /**
   * 执行 setup 函数
   */
  if (isFunction(type.setup)) {
    const setupContext = createSetupContext(instance)
    instance.setupContext = setupContext
    const setupResult = type.setup(instance.props, setupContext)

    /**
     * 处理 setup 返回值
     */
    handleSetupResult(instance, setupResult)
  }

  /**
   * 如果 setup 没有返回 render 函数
   * 则使用组件选项中的 render
   */
  if (!instance.render) {
    instance.render = type.render
  }
}

工作流程:

  1. 创建组件实例代理
  2. 执行 setup 函数,获取返回值
  3. 调用 handleSetupResult 处理返回值
  4. 如果 setup 没有返回渲染函数,则使用组件选项中的 render

设计亮点:

  • 优先使用 setup 返回的渲染函数
  • 保留向后兼容性,支持传统的 render 选项
  • 统一的返回值处理逻辑,易于维护

代码重构

将返回值处理逻辑抽取到独立函数,符合单一职责原则:

/**
 * 处理 setup 函数的返回值
 */
function handleSetupResult(instance, setupResult) {
  if (isFunction(setupResult)) {
    instance.render = setupResult
  }
  else if (isObject(setupResult)) {
    instance.setupState = proxyRefs(setupResult)
  }
}

/**
 * 初始化有状态组件
 */
function setupStatefulComponent(instance) {
  const { type } = instance

  instance.proxy = new Proxy(instance.ctx, publicInstanceProxyHandlers)

  if (isFunction(type.setup)) {
    const setupContext = createSetupContext(instance)
    instance.setupContext = setupContext
    const setupResult = type.setup(instance.props, setupContext)
    handleSetupResult(instance, setupResult)
  }

  if (!instance.render) {
    instance.render = type.render
  }
}

架构优势:

  1. 单一职责handleSetupResult 专注于处理 setup 返回值
  2. 易于测试:可以单独测试返回值处理逻辑
  3. 可扩展性:未来支持其他类型的返回值时,只需修改 handleSetupResult

总结

至此,我们完成了 Setup 渲染函数返回的核心实现:

1. Setup 返回值类型判断

  • 使用 isFunction 判断是否为渲染函数
  • 使用 isObject 判断是否为状态对象

2. 渲染函数绑定

  • setup 返回的函数直接赋值到 instance.render
  • 作为组件的渲染函数使用

3. 状态对象处理

  • setup 返回的对象使用 proxyRefs 包装
  • 赋值到 instance.setupState,支持响应式访问

4. 渲染函数优先级

  • setup 返回的 render 函数优先级最高
  • 组件选项中的 render 函数作为后备选项

5. 代码重构

  • 将返回值处理逻辑抽取到 handleSetupResult
  • 符合单一职责原则,提升可维护性

这套机制让开发者在使用 Composition API 时,能够以更灵活的方式定义组件的渲染逻辑,同时保持与 Options API 的兼容性。无论是返回渲染函数还是返回状态对象,Vue 都能正确识别和处理,是 Vue 3 提供强大开发体验的重要组成部分。