无渲染组件是"带生命周期的逻辑容器",组合式函数是"轻量级的逻辑原子"。
一、什么是无渲染组件(Renderless Components)
它指的是组件本身不输出任何DOM节点,只负责封装状态和逻辑,通过**作用域插槽(Scoped Slots)**将内部数据和方法暴露给父组件,由父组件决定UI长什么样。
典型 Demo:鼠标追踪器(Mouse Tracker)
1. 无渲染组件 MouseTracker.vue
vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 只封装逻辑,没有任何UI
const x = ref(0)
const y = ref(0)
const update = (e) => {
x.value = e.clientX
y.value = e.clientY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 关键:通过默认插槽把数据传给父组件
</script>
<template>
<slot :x="x" :y="y"></slot>
</template>
2. 父组件使用(完全自定义UI)
vue
<template>
<!-- 用法1:显示坐标文本 -->
<MouseTracker v-slot="{ x, y }">
<p>鼠标位置:{{ x }}, {{ y }}</p>
</MouseTracker>
<!-- 用法2:绘制成圆形跟随鼠标 -->
<MouseTracker v-slot="{ x, y }">
<div :style="{ left: x + 'px', top: y + 'px', position: 'fixed' }"
class="circle" />
</MouseTracker>
</template>
二、无渲染组件 vs 组合式函数(核心区别)
两者都能复用逻辑,但定位和适用场景截然不同。下表直击本质:
| 对比维度 | 无渲染组件 (Renderless) | 组合式函数 (Composables) |
|---|---|---|
| 本质形态 | Vue组件实例(有独立的生命周期、依赖注入上下文) | 普通的JavaScript函数(只是逻辑的集合) |
| 生命周期钩子 | ✅ 可以使用 onMounted、onUnmounted 等(如上例) |
✅ 也可以使用(因为其在组件上下文中调用) |
| 性能开销 | 较高(需要创建组件实例,纳入Virtual DOM树) | 极低(仅是函数调用,无实例开销) |
| 状态归属 | 状态属于组件实例。多个引用会创建多个独立实例 | 状态属于调用它的组件。多次调用也会生成独立状态 |
| 渲染控制权 | 父组件通过**插槽(Slot)**接收数据,UI完全由父组件定义 | 父组件直接在 <template> 中调用函数返回的数据和方法,UI控制更直接 |
| 跨层级通信 | 天然支持 provide/inject,可充当"逻辑中间层"向下传递 |
需要手动在使用处传递,或结合 provide/inject |
| 适用场景 | 需要监听生命周期 、复用DOM事件绑定、且UI变化极大的场景(如拖拽、下拉菜单、图表容器) | 纯业务逻辑 、数据请求 、状态管理 、没有DOM依赖的场景 |
三、为什么有这种差异?(深度辨析)
1. 生命周期归属不同(最核心)
- 组合式函数虽然也能用
onMounted,但它是寄生在调用它的组件身上的。如果组件被销毁,函数里的监听就没了。 - 无渲染组件拥有独立的
onMounted/onUnmounted,这使得它非常适合封装"需要独立挂载/卸载"的DOM副作用(如全局事件监听、定时器、Intersection Observer)。
2. 模板渲染的"黑盒"程度
- 组合式函数返回的是裸数据 ,父组件需要自己在模板里写
{``{ data }},逻辑与UI混合在父组件模板中。 - 无渲染组件把逻辑完全隔离 ,父组件通过插槽拿到数据,强迫你分开逻辑与UI,适合组件库内部构建高度可定制的"基座"。
3. 实际开发怎么选?
- 用组合式函数 (优先选):纯数据处理、表单校验、接口请求、封装
localStorage读写。90%的场景用它就够了。 - 用无渲染组件 (特定场景):当你的逻辑依赖DOM挂载/卸载 (比如监听
window尺寸、拖拽范围检测),并且需要把控制权完整交给不同UI 时。比如 Element Plus 的ElPopper底层就是无渲染思路。
四、最佳实践小贴士
如果你发现组合式函数里写了大量 onMounted 和 onUnmounted 去操作 window 或 document,说明它更适合改成无渲染组件,因为这样可以避免因父组件重复渲染导致监听被意外卸载。
反过来,如果无渲染组件里只是 ref 和 computed 的加减乘除,那它纯属多余,改成组合式函数更轻量。
一句话总结:组合式函数是"逻辑的瑞士军刀",随取随用;无渲染组件是"逻辑的独立工位",自带电源(生命周期)和操作台(插槽),但占地方(性能开销)。💡