Using and Building Custom Hooks in Vue 3
This article explains what Hooks are, demonstrates how to use them in Vue 3, and guides readers through building reusable custom Hook functions such as useMouse and useTable, covering pagination, parameter handling, and best practices for clean, maintainable frontend code.
Vue 3 defines a composable (Hook) as a function that leverages the Composition API to encapsulate and reuse stateful logic, a concept borrowed from React Hooks. The article starts by introducing the definition and the difference between ordinary functions and Hook functions that contain reactive state.
It first shows a plain implementation that tracks mouse coordinates directly in a component, requiring duplicated code and lifecycle hooks ( onMounted , onUnmounted ) for each usage:
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const x = ref(0);
const y = ref(0);
function update(event) { x.value = event.pageX; y.value = event.pageY; }
onMounted(() => window.addEventListener('mousemove', update));
onUnmounted(() => window.removeEventListener('mousemove', update));
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>Next, the same functionality is refactored into a custom Hook useMouse , dramatically simplifying component code:
<script setup>
import { useMouse } from './mouse.js';
const { x, y } = useMouse();
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>The tutorial then builds a more practical Hook useTable for fetching table data. An initial version returns an array [data, refresh] and abstracts the API call, reducing repetitive code when multiple tables are present.
import { ref } from 'vue';
export function useTable(api) {
const data = ref([]);
const refresh = () => api().then(res => data.value = res);
refresh();
return [data, refresh];
}To handle pagination, sorting, and loading states, the article introduces usePagination and extends useTable to accept options for data paths, immediate execution, and custom parameters. The enhanced Hook returns [data, refresh, loading, pagination] and supports automatic total count handling, loading indicators, and flexible request payload construction.
export function useTable(api, options = {}) {
const [pagination, , , setTotal] = usePagination(() => refresh());
const data = ref([]);
const loading = ref(false);
const refresh = () => {
loading.value = true;
return api({ page: pagination.current, limit: pagination.size })
.then(res => { data.value = get(res, options.path?.data, []); setTotal(get(res, options.path?.total, 0)); })
.finally(() => { loading.value = false; });
};
options.immediate && refresh();
return [data, refresh, loading, pagination];
}Advanced parameter strategies are discussed, including passing static objects, reactive objects, or functions that return parameter objects, allowing the Hook to adapt to changing query criteria such as user‑selected IDs. The final version also lets the refresh function accept extra parameters at call time, making the Hook suitable for complex UI interactions.
const refresh = (extraData) => {
const request = { page: pagination.current, size: pagination.size, ...(extraData || {}), ...(typeof params === 'function' ? params() : params) };
loading.value = true;
return api(request).then(res => { data.value = get(res, options.path?.data, []); setTotal(get(res, options.path?.total, 0)); })
.finally(() => { loading.value = false; });
};In conclusion, the article demonstrates how Hooks enable stateful logic reuse in Vue, improve code readability, and, when combined with component encapsulation, lead to highly maintainable frontend applications.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.