TLDR
Group related logic in your Vue components into well-named functions inside <script setup>. The technique adapts Martin Fowler’s Extract Function pattern to keep components readable without splitting them into separate files.
Introduction
The Composition API and <script setup> give you flexibility, which lets you cram queries, state, side effects, and business logic into one block. Components turn into a tangle.
Use Extract Function to clean up the mess. Michael Thiessen named the Vue-specific version “inline composables” in his post at michaelnthiessen.com/inline-composables.
The pattern comes from Martin Fowler’s Refactoring catalog. Fowler describes it as breaking large functions into smaller ones with descriptive names. You can read his explanation at refactoring.com/catalog/extractFunction.html.
Fowler’s example:
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
// print details
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
Extract the details-printing logic into its own function:
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(outstanding);
function printDetails(outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
}
The top-level function now reads like a story.
Bringing Extract Function to Vue
Apply the same principle inside Vue components with inline composables: small functions declared in your <script setup> block that own a specific piece of logic.
The example below builds on a gist from Evan You.
Before Refactoring
The original component mixes everything in one block:
// src/components/FolderManager.vue
<script setup>
import { ref, watch } from 'vue'
async function toggleFavorite(currentFolderData) {
await mutate({
mutation: FOLDER_SET_FAVORITE,
variables: {
path: currentFolderData.path,
favorite: !currentFolderData.favorite
}
})
}
const showHiddenFolders = ref(localStorage.getItem('vue-ui.show-hidden-folders') === 'true')
const favoriteFolders = useQuery(FOLDERS_FAVORITE, [])
watch(showHiddenFolders, (value) => {
if (value) {
localStorage.setItem('vue-ui.show-hidden-folders', 'true')
} else {
localStorage.removeItem('vue-ui.show-hidden-folders')
}
})
</script>
It works, but you have to read every line to figure out what the component does.
After Refactoring with Inline Composables
Group the logic into focused composables:
// src/components/FolderManager.vue
<script setup>
import { ref, watch } from 'vue'
import { useQuery, mutate } from 'vue-apollo'
import FOLDERS_FAVORITE from '@/graphql/folder/favoriteFolders.gql'
import FOLDER_SET_FAVORITE from '@/graphql/folder/folderSetFavorite.gql'
const { showHiddenFolders } = useHiddenFolders()
const { favoriteFolders, toggleFavorite } = useFavoriteFolders()
function useHiddenFolders() {
const showHiddenFolders = ref(localStorage.getItem('vue-ui.show-hidden-folders') === 'true')
watch(showHiddenFolders, (value) => {
if (value) {
localStorage.setItem('vue-ui.show-hidden-folders', 'true')
} else {
localStorage.removeItem('vue-ui.show-hidden-folders')
}
}, { lazy: true })
return { showHiddenFolders }
}
function useFavoriteFolders() {
const favoriteFolders = useQuery(FOLDERS_FAVORITE, [])
async function toggleFavorite(currentFolderData) {
await mutate({
mutation: FOLDER_SET_FAVORITE,
variables: {
path: currentFolderData.path,
favorite: !currentFolderData.favorite
}
})
}
return {
favoriteFolders,
toggleFavorite
}
}
</script>
The top of the script now spells out the component’s responsibilities:
const { showHiddenFolders } = useHiddenFolders();
const { favoriteFolders, toggleFavorite } = useFavoriteFolders();
Each composable carries a descriptive name. The implementation details sit below, out of the way until you need them.
Best Practices
- Reach for inline composables once your
<script setup>gets hard to read - Group related state, watchers, and async logic by responsibility
- Give composables clear names that explain their purpose
- Keep each composable focused on a single concern
- Move a composable to its own file once you reuse it across components
When to Use Inline Composables
- Your component contains related pieces of state and logic
- The logic belongs to this component and isn’t ready for sharing
- You want better readability without adding a new file
- You need to organize complex component logic without over-engineering
Conclusion
Inline composables apply Martin Fowler’s Extract Function to Vue. You get:
- Component code organized by responsibility
- Cleaner separation of concerns
- A path toward reusable composables once the logic earns its own file
Pick a cluttered component in your codebase and try it. The refactor takes minutes, and you’ll thank yourself the next time you read the file.
You can see the full example in Evan You’s gist: https://gist.github.com/yyx990803/8854f8f6a97631576c14b63c8acd8f2e