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
这种设计让组件的定义更加灵活:
- 如果只需要返回渲染函数,可以直接在
setup中返回 - 如果需要返回多个响应式状态,可以返回对象
实现 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
}
}
工作流程:
- 创建组件实例代理
- 执行
setup函数,获取返回值 - 调用
handleSetupResult处理返回值 - 如果
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
}
}
架构优势:
- 单一职责:
handleSetupResult专注于处理 setup 返回值 - 易于测试:可以单独测试返回值处理逻辑
- 可扩展性:未来支持其他类型的返回值时,只需修改
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 提供强大开发体验的重要组成部分。