ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vue3自定义Hook让代码清爽如诗

Vue3自定义Hook让代码清爽如诗 说实话用 Vue3 组合式 API 写了半年项目之后最大的感受就是同样的逻辑别再复制粘贴了。我记得刚切换到 Vue3 那会儿还是带着 Vue2 的思维习惯——一个页面里 data、methods、computed、watch 各占一块功能相关的代码散落在各个角落。改一个功能得在四个选项之间跳来跳去页面稍微复杂一点一个文件就奔着五六百行去了-12。后来慢慢悟了组合式 API 最大的价值就是把逻辑按“功能”而不是“类型”来组织。今天咱们就来聊聊怎么通过自定义 Hooks 把重复的业务逻辑抽出来让代码清爽起来。1. 从 Options API 到 Composition API到底变了什么先来看一个典型的场景一个页面需要跟踪鼠标位置同时还有一个计数器。在 Vue2 的 Options API 里代码可能是这样的export default { data() { return { count: 0, x: 0, y: 0 }; }, mounted() { window.addEventListener(mousemove, this.updateMouse); }, beforeUnmount() { window.removeEventListener(mousemove, this.updateMouse); }, methods: { increment() { this.count; }, updateMouse(e) { this.x e.pageX; this.y e.pageY; } } };两个完全不相关的功能——计数和鼠标追踪——硬生生挤在同一个对象里。如果页面再复杂一点还有搜索、分页、表单校验……那画面太美我不敢看-3。Vue3 的组合式 API 允许我们把每个功能封装成一个独立的函数然后在setup里按需组合script setup import { useCounter } from /composables/useCounter; import { useMouse } from /composables/useMouse; const { count, increment } useCounter(); const { x, y } useMouse(); /script三行代码功能一目了然。这就是自定义 Hooks 的魅力。2. 自定义 Hook 到底是什么怎么命名说白了自定义 Hook 就是一个函数它把一组相关的响应式状态和逻辑封装在一起返回给组件使用-1-8。有几个约定俗成的规矩文件名以use开头比如useCounter、useFetch、useLocalStorage返回一个对象或数组包含响应式数据和操作方法内部可以使用 Vue 的响应式 API 和生命周期钩子来看一个最经典的例子——封装一个鼠标追踪器// composables/useMouse.js import { ref, onMounted, onUnmounted } from vue; export function useMouse() { const x ref(0); const y ref(0); const update (e) { x.value e.pageX; y.value e.pageY; }; onMounted(() { window.addEventListener(mousemove, update); }); onUnmounted(() { window.removeEventListener(mousemove, update); }); return { x, y }; }组件里直接用template p鼠标位置{{ x }}, {{ y }}/p /template script setup import { useMouse } from /composables/useMouse; const { x, y } useMouse(); /script干净利落鼠标相关的所有逻辑都在 Hook 里组件只负责展示-2-9。3. 实战场景一封装通用的数据请求 Hook项目中调接口是家常便饭每个组件都写一遍loading、error、data的状态管理太重复了。抽一个useFetch出来// composables/useFetch.ts import { ref } from vue; export function useFetchT(url: string) { const data refT | null(null); const loading ref(false); const error refError | null(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 (err) { error.value err instanceof Error ? err : new Error(未知错误); } finally { loading.value false; } }; // 自动执行一次 fetchData(); return { data, loading, error, refetch: fetchData }; }组件里这样用script setup import { useFetch } from /composables/useFetch; const { data, loading, error, refetch } useFetchUser[](/api/users); /script template div v-ifloading加载中.../div div v-else-iferror出错了{{ error.message }}/div ul v-else li v-foruser in data :keyuser.id{{ user.name }}/li /ul button clickrefetch刷新/button /template一个 Hook 搞定加载状态、错误处理、数据展示所有调接口的地方都能复用-1-5。4. 实战场景二封装验证码倒计时验证码倒计时也是高频场景。封装成 Hook 之后任何需要发送验证码的组件都能直接用// composables/useCountDown.js import { ref, onUnmounted } from vue; export function useCountDown() { const count ref(0); let timer null; const start (seconds 60, onEnd () {}) { if (count.value 0) return; count.value seconds; timer setInterval(() { count.value--; if (count.value 0) { clearInterval(timer); timer null; onEnd(); } }, 1000); }; onUnmounted(() { if (timer) clearInterval(timer); }); return { count, start }; }组件里用起来就一行script setup import { useCountDown } from /composables/useCountDown; const { count, start } useCountDown(); const sendCode () { // 调发送验证码接口 start(60, () console.log(倒计时结束)); }; /script template button :disabledcount 0 clicksendCode {{ count 0 ? ${count}s后重试 : 发送验证码 }} /button /template这个 Hook 封装了定时器的创建、清除、倒计时状态组件只需要关心“什么时候开始”和“结束后做什么”-7。5. 进阶技巧组合多个 HookVue3 的 Hook 支持互相组合这是它比 Vue2 mixins 强大的地方。比如你想做一个“鼠标是否在某个元素内”的判断可以把useMouse和useElementBounding组合起来// composables/useMouseInElement.js import { computed, ref } from vue; import { useMouse } from ./useMouse; export function useMouseInElement(elementRef) { const { x, y } useMouse(); const isInside computed(() { if (!elementRef.value) return false; const rect elementRef.value.getBoundingClientRect(); return x.value rect.left x.value rect.right y.value rect.top y.value rect.bottom; }); return { x, y, isInside }; }这种“搭积木”式的组合让代码复用达到了一个新的高度-6。6. 封装 Hooks 的几个原则踩了一些坑之后我总结了几条实用的原则① 单一职责一个 Hook 只做一件事。useFetch只管请求useCounter只管计数别揉在一起-12。② 返回 readonly 状态如果数据不需要外部直接修改用readonly包一层防止组件内部意外篡改-6import { readonly } from vue; return { data: readonly(data), loading, error };③ 参数用对象形式当 Hook 的参数超过 2 个时用对象传参比位置参数好维护得多// 不好 useFetch(/api/users, POST, { token: xxx }, 5000); // 好 useFetch(/api/users, { method: POST, headers: { token: xxx }, timeout: 5000 });④ 清理副作用在onUnmounted里清理定时器、事件监听、请求取消等防止内存泄漏-2。7. 和 Mixins 相比好在哪里用过 Vue2 mixins 的同学都知道多个 mixin 之间的命名冲突、依赖不清晰、来源不明调试起来简直是噩梦。Hook 是纯粹的 JavaScript 函数参数和返回值都是显式的IDE 能自动提示类型推导也友好得多-9。而且同一个 Hook 可以在一个组件里用多次互不干扰const counter1 useCounter(0); const counter2 useCounter(10); // 两个计数器各自独立这在 mixins 里是做不到的。总结一下自定义 Hooks 的核心思想就是把“和某功能相关的所有代码”放在一起而不是把“同类型的代码”放在一起。数据请求、表单校验、鼠标追踪、倒计时、滚动监听……这些业务逻辑一旦抽成 Hook就能在多个组件间无缝复用页面代码量肉眼可见地减少。刚开始抽 Hooks 的时候可能会纠结“这要不要抽”我的建议是同一个逻辑出现两次以上就值得抽。不用一步到位慢慢来写着写着手感就来了。如果你也有自己封装过的实用 Hook欢迎在评论区分享一下思路互相学习学习。觉得有用的话点个收藏下次写组件的时候翻出来看看~
返回列表