Your First Composable
Components grow. In Lifecycle and Data, loading the menu took three refs and an onMounted, and every other component that loads something would need the same lines again. Vue's answer is a composable: a function that uses Vue's ref, computed, watch or lifecycle hooks, and returns what a component needs. Its name starts with use, so that you can tell it's one: useCounter, useLocalStorage, useFetch.
Here's a counter as a composable. It's in a file of its own, useCounter.js:
import { ref } from "vue";
export function useCounter(start) {
const count = ref(start);
function increase() {
count.value++;
}
return { count, increase };
}And here's a component that uses it:
<script setup>
import { useCounter } from "./useCounter.js";
const { count, increase } = useCounter(0);
</script>
<template>
<p>Cups of coffee today: {{ count }}</p>
<button @click="increase">+1 cup</button>
</template>What's new:
- A composable is in a
.jsfile, not a.vuefile: it has no template and no styles, only JavaScript. It imports from"vue"what it uses. export functionexports the function by its name, asexport constdid in the lesson Components. So the component imports it in braces, as it importsreffrom"vue":import { useCounter } from "./useCounter.js";.- The component calls the composable at the top of
<script setup>, as it callsref(). The composable creates the ref and the function then, and returns them in an object, and destructuring takes them out:const { count, increase } = useCounter(0);. - From then on,
countandincreasework as if they had been written in the component itself:countis a ref, which{{ count }}shows, andincreaseis a function for@click.
Each call creates a new ref: two components that call useCounter() each have a count of their own. Calling a composable is like copying its lines into the component, except that they're written only once.
Typical mistakes:
import useCounter from "./useCounter.js";without the braces: that imports the file's default export, which it doesn't have. The page stays empty, with aTypeErrorin the console.- Forgetting
return: the function then returnsundefined, and the destructuring ends with aTypeErrorthat startsCannot destructure property 'count'.
Instructions
- 1
Move the counter into a composable. In
useCounter.js, writeuseCounter(start)as shown above. InApp.vue, import it, and replace the ref and the function withconst { count, increase } = useCounter(0);. The page works as before. - 2
Add a function
reset()to the composable that setscountback tostart, the number it started with, and return it too. InApp.vue, take it out with the others, and add a buttonResetafter+1 cupthat calls it.