Module: Vue Composition API

Composable functions

## 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:

  1. Import necessary APIs: We import ref to create reactive variables and onMounted to run code after the component is mounted.
  2. Define the function: The function is named useMousePosition following the convention.
  3. Create reactive state: x and y are created as reactive refs to store the mouse coordinates.
  4. Define logic: updatePosition is a function that updates the x and y values based on the mouse event.
  5. Lifecycle Hook: onMounted registers the updatePosition function as a listener for the mousemove event.
  6. Return reactive state: The function returns an object containing the reactive x and y variables. 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:

  1. Import the composable: We import the useMousePosition function.
  2. Call the composable in setup(): Inside the setup() function, we call useMousePosition(). This returns an object with the x and y reactive variables.
  3. Return the reactive state: We return the x and y variables from the setup() 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/inject with Composables: You can use provide and inject to 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, and toRaw to control the reactivity of your state.
  • Composable Factories: Create functions that return composable functions, allowing for even more flexible configuration.

Resources