Vue3 自定義 Hook:讓你的代碼像樂高一樣“可復用”!
大家好,我是你們的前端小伙伴!上一篇我們聊了 Vue3 的生命周期,今天咱們繼續深入 Vue3 的核心利器——自定義 Hook(Custom Hook)。
如果你已經用過 ref
、reactive
、watch
、onMounted
這些 Composition API,那你已經“會用工具了”。而今天我們要學的,是如何把一堆工具打包成一個“超級工具包”,哪里需要就往哪里一放,功能立馬就有了!
這個“超級工具包”,就是 自定義 Hook。
什么是自定義 Hook?
想象一下,你在寫一個電商網站,有 5 個頁面都需要“獲取用戶信息 + 檢查登錄狀態 + 顯示用戶頭像”。
如果你在每個頁面都重復寫一遍:
const userInfo = ref(null)
const isLoggedIn = ref(false)onMounted(async () => {const data = await fetch('/api/user')userInfo.value = dataisLoggedIn.value = data ? true : false
})watch(userInfo, (newVal) => {if (newVal) console.log('用戶已登錄')
})
那……恭喜你,代碼已經“復制粘貼”了 5 遍!如果哪天接口變了,你得改 5 個地方,想想都頭大。
自定義 Hook 就是:把這段“通用邏輯”抽出來,變成一個函數,誰需要誰調用!
自定義 Hook 的本質
它就是一個以 use
開頭的普通函數,比如 useUser
、useCart
、useLocalStorage
。
它內部可以使用任何 Composition API(ref
、watch
、onMounted
等),然后把需要暴露的數據和方法 return 出去。
動手寫一個:useUser()
?用戶信息 Hook
我們來寫一個真正可用的自定義 Hook!
// composables/useUser.js
import { ref, onMounted, watch } from 'vue'export function useUser() {const userInfo = ref(null)const isLoggedIn = ref(false)const loading = ref(true)// 模擬請求用戶數據const fetchUser = async () => {loading.value = truetry {const res = await fetch('/api/user')const data = await res.json()userInfo.value = dataisLoggedIn.value = !!data} catch (err) {console.error('獲取用戶失敗', err)} finally {loading.value = false}}// 組件掛載時自動獲取用戶onMounted(() => {fetchUser()})// 監聽用戶信息變化watch(userInfo, (newVal) => {if (newVal) {console.log(`歡迎回來,${newVal.name}!`)}})// 把你需要的東西 return 出去return {userInfo,isLoggedIn,loading,fetchUser // 也可以手動刷新}
}
文件名建議:
composables/useXxx.js
,這是 Vue 社區的約定。
在組件中使用它
現在,任何組件只要想用“用戶邏輯”,一句話搞定:
<!-- UserProfile.vue -->
<script setup>
import { useUser } from '@/composables/useUser'// 一行代碼,搞定用戶邏輯!
const { userInfo, isLoggedIn, loading, fetchUser } = useUser()
</script><template><div v-if="loading">加載中...</div><div v-else-if="isLoggedIn"><h2>歡迎,{{ userInfo.name }}!</h2><button @click="fetchUser">刷新</button></div><div v-else>請先登錄</div>
</template>
看到了嗎?組件代碼變得極其干凈,邏輯都被“封裝”到
useUser
里了。
自定義 Hook 的核心優勢
優勢 | 說明 |
---|---|
邏輯復用 | 一套登錄邏輯,10 個頁面都能用 |
解耦清晰 | 組件只管“視圖”,Hook 管“邏輯” |
易于測試 | 直接測試?useUser ?函數,不用渲染組件 |
類型友好 | TypeScript 能自動推導返回值類型 |
高級用法:支持參數和返回函數
自定義 Hook 不只是“固定邏輯”,它也可以很靈活!
示例:useLocalStorage
?—— 讓數據自動存到本地
// composables/useLocalStorage.js
import { ref, watch } from 'vue'export function useLocalStorage(key, initialValue) {// 從 localStorage 讀取,或用默認值const data = ref(JSON.parse(localStorage.getItem(key)) || initialValue)// 數據變化時,自動存到 localStoragewatch(data, (newVal) => {localStorage.setItem(key, JSON.stringify(newVal))}, { deep: true }) // deep: true 支持對象/數組return data
}
使用它:
// 計數器,刷新頁面也不會丟!
const count = useLocalStorage('count', 0)// 用戶設置
const settings = useLocalStorage('userSettings', { theme: 'dark' })
看,一個 Hook,支持任意 key 和默認值,真正做到了“通用”!
常見誤區 & 注意事項
誤區 1:在普通函數里用?ref
?就是 Hook?
No!只有在 setup
或 <script setup>
中調用的函數,才能使用 Composition API。你的 useXxx
函數必須在組件上下文中調用。
誤區 2:Hook 可以 return 模板?
不能!Hook 只負責邏輯和數據,模板還是得在組件里寫。
最佳實踐:命名規范
- 一定要以?
use
?開頭,比如?useMouse
、useScroll
、useFetch
- 返回值盡量結構清晰,方便解構使用
- 復雜邏輯可以拆分成多個小 Hook
實戰案例:useMouse
?—— 跟蹤鼠標位置
// 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.clientXy.value = e.clientY}onMounted(() => {window.addEventListener('mousemove', update)})onUnmounted(() => {window.removeEventListener('mousemove', update)})return { x, y }
}
使用:
<script setup>
import { useMouse } from '@/composables/useMouse'
const { x, y } = useMouse()
</script><template><div>鼠標位置:{{ x }}, {{ y }}</div>
</template>
瞬間,你的組件就有了“追蹤鼠標”的超能力!
為什么叫“Hook”?
“Hook” 的意思是“鉤子”,它就像是把一段邏輯“鉤”進組件的生命周期中。
比如 onMounted
就是一個“鉤子”,告訴 Vue:“等組件掛載后,執行我這個函數”。
而自定義 Hook,就是把多個鉤子和邏輯打包,變成一個可復用的“增強包”。
總結:自定義 Hook 的靈魂
關鍵點 | 說明 |
---|---|
目的 | 邏輯復用,避免重復代碼 |
形式 | useXxx() ?函數,返回響應式數據和方法 |
能力 | 可接收參數、可組合多個 API、可嵌套使用 |
好處 | 代碼更清晰、維護更容易、團隊協作更高效 |
最后一句話
組件負責“長什么樣”,Hook 負責“怎么動”。
把通用邏輯封裝成 Hook,你的代碼就會像樂高積木一樣,拼裝自由,復用無憂!