## Vue Composition API: Composable Functions
Composable functions are a core concept of the Vue Composition API, enabling you to extract and reuse stateful logic across multiple components. They are essentially JavaScript functions that utilize the Composition API's reactive primitives (like `ref`, `reactive`, `computed`, and `watch`) to encapsulate reusable functionality.
### What are Composable Functions?
* **Reusable Logic:** They allow you to extract complex logic from components, making your code more modular, maintainable, and testable.
* **Stateful:** They can manage and expose reactive state, just like the `setup()` function in a component.
* **Type-Safe (with TypeScript):** Composable functions can be strongly typed with TypeScript, improving code reliability.
* **Not Bound to Components:** Unlike methods within a component's `setup()` function, composables are independent and can be imported and used in any component.
* **Convention over Configuration:** Composable function names are conventionally prefixed with `use` (e.g., `useMousePosition`, `useFetch`). This isn't enforced, but it's a widely adopted practice for clarity.
### Why Use Composable Functions?
* **Code Reusability:** Avoid duplicating logic across multiple components.
* **Improved Organization:** Break down complex component logic into smaller, manageable units.
* **Enhanced Testability:** Composable functions are easier to test in isolation.
* **Better Maintainability:** Changes to reusable logic only need to be made in one place.
* **Logic Sharing:** Share logic between components without tight coupling.
### Creating a Composable Function
Here's a basic example of a composable function that tracks mouse position:
```javascript
import { ref, onMounted } from 'vue';
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
const updatePosition = (event) => {
x.value = event.clientX;
y.value = event.clientY;
};
onMounted(() => {
window.addEventListener('mousemove', updatePosition);
});
return {
x,
y,
};
}
Explanation:
- Import necessary APIs: We import
refto create reactive variables andonMountedto run code after the component is mounted. - Define the function: The function is named
useMousePositionfollowing the convention. - Create reactive state:
xandyare created as reactiverefs to store the mouse coordinates. - Define logic:
updatePositionis a function that updates thexandyvalues based on the mouse event. - Lifecycle Hook:
onMountedregisters theupdatePositionfunction as a listener for themousemoveevent. - Return reactive state: The function returns an object containing the reactive
xandyvariables. This is how the component accesses the state managed by the composable.
Using a Composable Function in a Component
<template>
<div>
Mouse position: x = {{ x }}, y = {{ y }}
</div>
</template>
<script>
import { useMousePosition } from './useMousePosition';
export default {
setup() {
const { x, y } = useMousePosition();
return {
x,
y,
};
},
};
</script>
Explanation:
- Import the composable: We import the
useMousePositionfunction. - Call the composable in
setup(): Inside thesetup()function, we calluseMousePosition(). This returns an object with thexandyreactive variables. - Return the reactive state: We return the
xandyvariables from thesetup()function, making them available in the template.
Passing Arguments to Composable Functions
Composable functions can accept arguments to customize their behavior.
// useFetch.js
import { ref, onMounted } from 'vue';
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
const loading = ref(true);
onMounted(async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
data.value = await response.json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
});
return {
data,
error,
loading,
};
}
<template>
<div>
<p v-if="loading">Loading...</p>
<p v-if="error">Error: {{ error.message }}</p>
<pre v-if="data">{{ data }}</pre>
</div>
</template>
<script>
import { useFetch } from './useFetch';
export default {
setup() {
const { data, error, loading } = useFetch('https://jsonplaceholder.typicode.com/todos/1');
return {
data,
error,
loading,
};
},
};
</script>
Advanced Techniques
provide/injectwith Composables: You can useprovideandinjectto make composable functions available to deeply nested components without explicitly passing them down through props.- TypeScript with Composables: Leverage TypeScript to define types for the arguments and return values of your composable functions, ensuring type safety.
- Reactivity Transforms: Use reactivity transforms like
shallowRef,readonly, andtoRawto control the reactivity of your state. - Composable Factories: Create functions that return composable functions, allowing for even more flexible configuration.
Resources
- Vue Composition API Documentation: https://vuejs.org/guide/composition-api.html
- Composable Functions in Vue 3: https://www.vuejsguide.net/composition-api/composables.html