Why does Vue 3 recommend composables over mixins and renderless components for reusing stateful logic between components?
answer
- where did this.loading come from?
- two sources, one key
- hidden coupling between reusable units
- extra instance per use
- logic only versus logic plus layout
basics
~20 sComposables make every reused name explicit at the call site, resolve collisions by renaming and pass data between units as arguments, fixing mixins' three flaws, and unlike renderless components they add no extra component instance.
solid answer
~40 sThe Vue 3 guide names three mixin problems: it is unclear which mixin supplied a property on `this`, two mixins can define the same key and collide, and mixins that cooperate do so through shared property names, coupling them invisibly. A composable fixes all three with ordinary function scoping: the caller destructures what it uses, renames on a collision (`const { loading: postsLoading } = usePosts()`), and passes one composable's output into another as an argument. Renderless components avoid those problems but create an extra component instance for every use. So the guidance is composables for pure logic and components when you reuse logic *and* layout; mixins stay only for migration and familiarity.
code
vue · 11 lines<script setup lang="ts">
import { useUser } from './useUser'
import { usePosts } from './usePosts'
import { useComments } from './useComments'
// Every name is declared here; the collision on `loading` is resolved by renaming.
const { user, loading: userLoading } = useUser()
const { posts, selectedId, loading: postsLoading } = usePosts(() => user.value?.id)
// Cross-unit dependency passed as an argument, not read from a shared `this` property.
const { comments } = useComments(selectedId)
</script>go deeper
Recall the three mixin drawbacks by name: unclear source, namespace collisions, implicit coupling between mixins.
Map each drawback to the composable feature that fixes it, and state the extra-instance cost of renderless components.
Show where the line falls in a real codebase: composables for logic, components for logic plus layout, and a plan for the mixins still present.
Frame the choice as a maintainability policy: explicit dependencies and typed reuse units across teams, and how to stop new mixins entering the codebase.
## Three ways to reuse stateful logic Vue has offered three mechanisms for sharing stateful logic between components: - **Mixins**: an options object merged into the component's own options. It was Vue 2's primary mechanism and is still supported in Vue 3. - **Renderless components**: a component that owns the logic and hands its state to the parent through a scoped slot, rendering no markup of its own. - **Composables**: plain functions that use the Composition API and return state. The Vue 3 guide recommends composables for pure logic, keeps components for logic plus layout, and no longer recommends mixins, which it keeps for migration and familiarity reasons. ## What goes wrong with mixins The guide names three drawbacks: 1. **Unclear source of properties.** A component with several mixins uses `this.loading` or `this.fetchUser()` with no local declaration. The reader has to open every mixin to find where each property comes from. 2. **Namespace collisions.** Mixins from different authors can register the same key. The clash is resolved by the options-merge rules rather than by a decision the component author made visibly. 3. **Implicit cross-mixin communication.** When one mixin needs data another provides, it reads a shared property name on `this`, coupling the two with nothing in either file to show it. ## How composables remove each problem - **Explicit source**: `const { user, loading } = useUser()` declares every name at the call site, and go-to-definition leads straight to the function. - **Renaming on collision**: `const { loading: postsLoading } = usePosts()` resolves a clash in the consumer with ordinary destructuring. - **Explicit data flow**: one composable's output is passed as an argument to another, as in `useComments(postId)`, so the dependency is visible and typed. These are ordinary JavaScript scoping properties. They are also why composables get TypeScript inference without extra typing machinery: a function's return type is inferred directly, while a mixin's contribution to `this` has to be computed by merging option types. ## Composables versus renderless components A renderless component reuses logic by being a component. It holds the state and exposes it through slot props, for example `<MouseTracker v-slot="{ x, y }">`. It works, and it predates the Composition API, but every use creates **an extra component instance**, with its own render effect and lifecycle. Across a large application those extra instances become a noticeable performance overhead. A composable adds no instance: its refs and watchers live in the calling component. The reverse also holds. A composable returns state and functions; it cannot supply markup. When the thing being reused is **logic together with visual layout**, a component is the right unit, and it may well use a composable internally. ## Side by side | | Mixin | Renderless component | Composable | |---|---|---|---| | Where state lives | merged into the component's `this` | a separate child instance | refs in the caller's scope | | Source of each name | implicit | explicit, via slot props | explicit, via destructuring | | Collision handling | options-merge rules | rename the slot prop | rename on destructure | | Extra instance per use | no | yes | no | | Meant for reusing markup | no | yes | no | | TypeScript inference | weak | depends on slot typing | direct | ## Answering the question well A strong answer does three things: 1. Names the concrete mixin drawbacks rather than saying "mixins are messy". 2. Shows the composable counterpart of each, ideally with the rename-on-destructure example. 3. Gives the boundary: composables for pure logic, components when layout is part of what is reused, mixins only in code that has not been migrated yet. Mixins remain in Vue 3 and many codebases carry them from Vue 2, so recognising them is expected. The detailed merge strategies and the step-by-step refactor from a mixin to a composable are a discussion about mixins themselves; the point here is why the composable is the recommended default.
- Is there anything a renderless component can do that a composable cannot?Yes: ship markup. A composable returns state and functions and cannot render. If the reusable unit includes a layout, a wrapper element or a particular slot structure, it should be a component, which can still use a composable internally for its logic.
- Why do composables get better TypeScript inference than mixins?A composable is a function, so TypeScript infers its return type directly and types every destructured variable from it. A mixin contributes properties to `this` through option merging, and inferring that merged instance type needs heavy type machinery that the Vue docs say still breaks down for mixins.
saying these in an interview costs you the question
- Mixins were removed in Vue 3, so composables are the only option left.
- A renderless component and a composable have the same runtime cost.
- A composable can replace any component, including one that reuses markup.
- Vue merges composable return values, so key collisions are resolved automatically.
- Composables are preferred mainly because they execute faster than mixins.