Vue 3 自定义 Hooks 设计与复用
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 最强大的特性之一,它让逻辑复用变得简单、直观且类型安全。