Building Reusable Vue 3 Logic with the Composition API's setup() Function
Learn how Vue 3's setup() function lets you encapsulate reactive state and logic into reusable composables, reducing duplication and keeping components clean.
19 Jan 2026, 22:42 UTC

The Problem: Duplicated Reactive Logic
When a Vue application grows, the same piece of state‑ful behavior often appears in multiple components—for example, a counter, a form‑validation handler, or a data‑fetching routine. Writing that logic inside each component’s options object leads to repetitive code, makes future changes error‑prone, and obscures the component’s primary responsibility.
Why setup() Solves It
The setup() function runs before the component instance is created and returns an object whose properties are exposed directly to the template. By placing reactive state, computed values, and watchers inside setup(), you keep the logic collocated with the component that uses it while still being able to extract it into a standalone function. This extracted function, commonly called a composable, can be imported and reused anywhere in the app without pulling in the Options API boilerplate.
Worked Example: A Counter Composable
Below is a step‑by‑step illustration of extracting a simple counter into a composable and using it in a component.
- Create a new Vue 3 project (if you don’t already have one):
npm init vue@latest my-vue-app cd my-vue-app npm install
Accept the default preset when prompted.
src/composables/useCounter.js:
import { ref, computed } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const double = computed(() => count.value * 2)
function increment() { count.value += 1 }
return { count, double, increment }
}
HelloWorld.vue script with a setup() that imports and uses the composable:
Count: {{ count }}
Double: {{ double }}
Add 1
npm run dev
Open the displayed URL, click the button, and observe that both the count and its double update instantly.
Trade‑off and Limitation
While composables promote reuse, over‑using reactive() on large, deeply nested objects can introduce performance penalties because each property access triggers a proxy trap. In such cases, consider splitting the state into smaller reactive pieces or using shallowReactive() for top‑level properties that do not need deep observation.
You can check whether a composable is causing unnecessary reactivity by opening Vue Devtools, selecting the component, and inspecting the count ref’s update frequency. If you see updates firing on unrelated interactions, the state may be overly granular.
Actionable Closing
Start by identifying a piece of logic that appears in at least two components. Extract it into a composable using ref(), computed(), and any needed watchers, then import it where needed. This approach keeps your components focused on presentation while centralizing reusable, testable behavior.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.