Files
agent-skills/skills/vue-best-practices/reference/slot-renderless-components-vs-composables.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

154 lines
4.1 KiB
Markdown

---
title: Consider Composables Instead of Renderless Components
impact: MEDIUM
impactDescription: Renderless slot components add overhead compared to composables for pure logic reuse
type: best-practice
tags: [vue3, slots, renderless-components, composables, performance, composition-api]
---
# Consider Composables Instead of Renderless Components
**Impact: MEDIUM** - Renderless components (components that only expose logic via scoped slots without rendering their own UI) were a popular pattern in Vue 2 for logic reuse. In Vue 3, Composition API composables are often more efficient and simpler for the same use case.
## Task Checklist
- [ ] Evaluate if a composable can replace a renderless component
- [ ] Use renderless components when slot-based composition is genuinely needed
- [ ] Prefer composables for pure logic/state sharing
- [ ] Consider component overhead vs function overhead
**Renderless Component Pattern:**
```vue
<!-- MouseTracker.vue - Renderless component -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>
<template>
<!-- Only renders the slot, no DOM of its own -->
<slot :x="x" :y="y" />
</template>
```
```vue
<!-- Usage -->
<MouseTracker v-slot="{ x, y }">
<div>Mouse: {{ x }}, {{ y }}</div>
</MouseTracker>
```
**Composable Alternative (Recommended):**
```typescript
// composables/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event: MouseEvent) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
```
```vue
<!-- Usage - simpler, no extra component -->
<script setup>
import { useMouse } from '@/composables/useMouse'
const { x, y } = useMouse()
</script>
<template>
<div>Mouse: {{ x }}, {{ y }}</div>
</template>
```
## When to Use Each Pattern
| Pattern | Best For |
|---------|----------|
| **Composables** | Pure logic reuse, state management, side effects |
| **Renderless Components** | When slot composition is needed, wrapper logic with flexible children |
### Use Composables When:
- Sharing reactive state and methods
- Encapsulating side effects (event listeners, timers)
- No need for template slot composition
- Maximum performance is needed
### Use Renderless Components When:
- Need to provide context/data to arbitrary slot content
- Building provider components (like context providers)
- The slot composition pattern is genuinely useful
- Building component libraries with maximum flexibility
## Real-World Examples
**Composable is Better:**
```typescript
// Data fetching
export function useFetch<T>(url: string) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(true)
// ... fetch logic
return { data, error, loading }
}
// Form validation
export function useForm(initialValues) {
const values = ref(initialValues)
const errors = ref({})
const validate = () => { /* ... */ }
return { values, errors, validate }
}
```
**Renderless Component is Better:**
```vue
<!-- Virtualized list that needs to wrap arbitrary item rendering -->
<VirtualScroller :items="items" :item-height="50">
<template #default="{ item, style }">
<!-- Consumer controls how each item renders -->
<div :style="style" class="custom-item">
{{ item.name }}
</div>
</template>
</VirtualScroller>
```
## Performance Consideration
Renderless components still:
- Create a component instance
- Go through the Vue component lifecycle
- Have reactive overhead
Composables are just functions:
- No component instance overhead
- Direct reactive primitives
- Smaller bundle size
## Reference
- [Vue.js Slots - Renderless Components](https://vuejs.org/guide/components/slots.html#renderless-components)
- [Vue.js Composables](https://vuejs.org/guide/reusability/composables.html)