Vue 3 自定义 Hooks 设计与复用

小飞兽 JavaScript 392 次阅读 2026-07-23

Vue 3 自定义 Hooks 设计与复用

Vue 3 的组合式 API 使得逻辑复用变得更加优雅。通过自定义 Hooks(也称为 Composables),可以将组件中可复用的逻辑抽取为独立的函数,在多个组件间共享。与 Vue 2 的 Mixin 相比,Hooks 更加透明——你可以在返回中明确看到 Hook 提供了什么属性和方法。

什么是 Hooks

Hooks 本质上是一个使用组合式 API 的函数,函数名通常以 use 开头。Hooks 可以调用 ref、reactive、computed、watch、生命周期钩子等 Vue 3 的响应式 API,返回的数据在组件中使用时会自动保持响应性。

// useWindowSize.js
import { ref, onMounted, onUnmounted } from "vue";

export function useWindowSize() {
const width = ref(window.innerWidth);
const height = ref(window.innerHeight);

const handleResize = () => {
width.value = window.innerWidth;
height.value = window.innerHeight;
};

onMounted(() => window.addEventListener("resize", handleResize));
onUnmounted(() => window.removeEventListener("resize", handleResize));

return { width, height };
}

// 在组件中使用
import { useWindowSize } from "@/hooks/useWindowSize";

export default {
setup() {
const { width, height } = useWindowSize();
return { width, height };
},
};

常用自定义 Hooks 示例

useLocalStorage:响应式本地存储

import { ref, watch } from "vue";

export function useLocalStorage(key, defaultValue) {
const stored = localStorage.getItem(key);
const data = ref(stored ? JSON.parse(stored) : defaultValue);

watch(data, (newVal) => {
if (newVal === null || newVal === undefined) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, JSON.stringify(newVal));
}
}, { deep: true });

return data;
}

// 使用
const theme = useLocalStorage("theme", "light");
theme.value = "dark"; // 自动同步到 localStorage

useFetch:数据请求封装

import { ref } from "vue";

export function useFetch(url) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);

const fetchData = async () => {
loading.value = true;
error.value = null;
try {
const res = await fetch(url);
if (!res.ok) throw new Error(HTTP ${res.status});
data.value = await res.json();
} catch (e) {
error.value = e.message;
} finally {
loading.value = false;
}
};

return { data, loading, error, fetch: fetchData };
}

// 使用
const { data, loading, error, fetch } = useFetch("/api/users");
fetch();

useDebounce:防抖 Hook

import { ref, watch } from "vue";
import { debounce } from "lodash-es";

export function useDebounce(value, delay = 300) {
const debouncedValue = ref(value.value);
let debouncedFn;

watch(value, (newVal) => {
debouncedFn = debounce(() => {
debouncedValue.value = newVal;
}, delay);
debouncedFn();
});

return debouncedValue;
}

带配置的 Hooks

// usePagination.js
import { ref, computed } from "vue";

export function usePagination(items, pageSize = 10) {
const currentPage = ref(1);

const totalPages = computed(() => Math.ceil(items.value.length / pageSize));

const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * pageSize;
return items.value.slice(start, start + pageSize);
});

const nextPage = () => {
if (currentPage.value < totalPages.value) currentPage.value++;
};

const prevPage = () => {
if (currentPage.value > 1) currentPage.value--;
};

const goToPage = (page) => {
currentPage.value = Math.max(1, Math.min(page, totalPages.value));
};

return {
currentPage,
totalPages,
paginatedItems,
nextPage,
prevPage,
goToPage,
};
}

组合多个 Hooks

// useUserDashboard.js
import { computed } from "vue";
import { useUser } from "./useUser";
import { useUserPosts } from "./useUserPosts";
import { useUserSettings } from "./useUserSettings";

export function useUserDashboard(userId) {
const { user, loading: userLoading, error: userError } = useUser(userId);
const { posts, loading: postsLoading } = useUserPosts(userId);
const { settings } = useUserSettings(userId);

const isLoading = computed(() => userLoading.value || postsLoading.value);

const stats = computed(() => ({
postsCount: posts.value.length,
activeDays: settings.value?.activeDays || 0,
}));

return { user, posts, settings, stats, isLoading, userError };
}

Hooks 的生命周期

Hooks 中可以使用 Vue 3 的所有生命周期钩子:onMounted、onUpdated、onUnmounted、onBeforeMount、onBeforeUpdate、onBeforeUnmount 等。这些钩子在调用 Hook 的组件上下文中执行。

export function useInterval(callback, delay) {
  const timer = ref(null);

const start = () => {
if (timer.value) return;
timer.value = setInterval(callback, delay);
};

const stop = () => {
if (timer.value) {
clearInterval(timer.value);
timer.value = null;
}
};

onUnmounted(stop); // 组件卸载时自动清理

return { start, stop };
}

注意事项

    • Hooks 的命名规范以 use 开头,这是 Vue 社区的约定
    • Hooks 之间可以相互调用,组合出更复杂的逻辑
    • Hooks 中创建的计算属性和监听器会在组件卸载时自动清理,无需手动处理
  • Hooks 返回的数据应该是响应式的(ref 或 reactive),否则在组件中使用时不会保持响应性
  • 避免在 Hook 中直接修改传入的 props,应该通过事件或回调通知父组件

自定义 Hooks 是 Vue 3 组合式 API 最强大的特性之一,它让逻辑复用变得简单、直观且类型安全。