VOOZH about

URL: https://alexop.dev/posts/inline-vue-composables-refactoring/

⇱ The Inline Vue Composables Refactoring pattern | alexop.dev


Next Talk: Automating Web Development with Claude Code

July 1, 2026 — DWX Developer World, Mannheim

Conference

The Inline Vue Composables Refactoring pattern

Published: at 

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

Stay Updated!

Subscribe to my newsletter for more TypeScript, Vue, and web dev insights directly in your inbox.

  • Background information about the articles
  • Weekly Summary of all the interesting blog posts that I read
  • Small tips and trick
Subscribe Now
Share this post on:
Share this post via WhatsAppShare this post on FacebookTweet this postShare this post via TelegramShare this post on PinterestShare this post via emailShare this post on LinkedIn