學習參考視頻:尚硅谷Vue3入門到實戰,最新版vue3+TypeScript前端開發教程_嗶哩嗶哩_bilibili
vue3學習目標:
VUE 3 | 1、Vue3架構與設計理念 |
2、組合式API(Composition API) | |
3、常用API:ref、reactive、watch、computed | |
4、Vue3的生命周期 | |
5、組件間通信(props、emit、defineModel) | |
6、了解插槽 |
Vue 生命周期鉤子詳解(Vue 2 & Vue 3 對比)
生命周期概念圖解
https://cn.vuejs.org/assets/lifecycle_zh-CN.W0MNXI0C.png
生命周期階段對比表
階段 | Vue 2 鉤子 | Vue 3 鉤子 | 觸發時機 | 典型用途 |
---|---|---|---|---|
?創建? | beforeCreate | setup() | 組件實例剛被創建 | 初始化響應式數據 |
created | setup() | 數據觀測/事件配置完成 | 異步請求數據 | |
?掛載? | beforeMount | onBeforeMount | 模板編譯/掛載前 | 訪問/操作DOM前的最后機會 |
mounted | onMounted | 組件掛載到DOM后 | 操作DOM、初始化第三方庫 | |
?更新? | beforeUpdate | onBeforeUpdate | 數據變化,DOM更新前 | 獲取更新前的DOM狀態 |
updated | onUpdated | 虛擬DOM重新渲染后 | 執行依賴DOM的操作 | |
?銷毀? | beforeDestroy | onBeforeUnmount | 實例銷毀前 | 清除定時器/取消訂閱 |
destroyed | onUnmounted | 實例銷毀后 | 清理內存/移除事件監聽 |
Vue 3 生命周期示例
<template><div class="demo"><h2>計數器: {{ count }}</h2><button @click="increment">增加</button><button @click="showComponent = false">卸載組件</button><ChildComponent v-if="showComponent" /></div>
</template><script setup lang="ts">
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
import ChildComponent from './ChildComponent.vue'// 1. 創建階段(setup)
const count = ref(0)
const showComponent = ref(true)console.log('1. setup階段 - 數據初始化完成')// 3. 掛載階段
onMounted(() => {console.log('3. mounted - 組件已掛載到DOM')// 這里可以安全地操作DOMdocument.title = `當前計數: ${count.value}`
})// 4. 更新階段
onUpdated(() => {console.log('4. updated - DOM已更新')// 避免在這里修改狀態,可能導致無限循環!
})// 方法
function increment() {count.value++console.log('2. 數據變化觸發更新流程')
}// 子組件示例
const ChildComponent = {setup() {onMounted(() => {console.log('子組件 mounted')})onUnmounted(() => {console.log('子組件 unmounted')})return {}},template: '<div>子組件內容</div>'
}
</script>
生命周期執行順序演示
-
組件初始化時:
1. setup階段 - 數據初始化完成 3. mounted - 組件已掛載到DOM
-
點擊"增加"按鈕時:
2. 數據變化觸發更新流程 4. updated - DOM已更新
-
點擊"卸載組件"時:
子組件 unmounted
?掛載階段完整順序(父 → 子)
- ?父組件??
setup()
?執行 - ?父組件??
onBeforeMount
- ?子組件??
setup()
?執行 - ?子組件??
onBeforeMount
- ?子組件??
onMounted
(子組件先完成掛載) - ?父組件??
onMounted
(父組件最后完成掛載)
常用鉤子使用場景
1. onMounted - 最常用
onMounted(async () => {// 加載初始數據const response = await fetch('/api/data')data.value = await response.json()// 初始化圖表chart = new Chart(document.getElementById('chart'), {// 配置項})
})
2. onUpdated - 謹慎使用
onUpdated(() => {// 典型用例:自動滾動到底部listContainer.value.scrollTop = listContainer.value.scrollHeight
})
3. onUnmounted - 資源清理
onUnmounted(() => {// 清除定時器clearInterval(timer)// 取消事件監聽window.removeEventListener('resize', handleResize)// 銷毀第三方庫實例chart?.destroy()
})
Vue 2 與 Vue 3 生命周期對比
關鍵區別:
- ?Vue 3 使用
setup
替代了beforeCreate
和created
- ?所有鉤子都需要顯式導入?
- ?命名更語義化(unmount 替代 destroy)??
- ?組合式API讓相關代碼更集中?
補充 自定義Hooks
什么是自定義 Hook?
自定義 Hook 是 Vue 3 組合式 API 的核心實踐方式,本質是一個封裝了響應式邏輯的函數。它解決了兩個核心問題:
- ?邏輯復用? - 避免重復代碼
- ?關注點分離? - 讓組件更專注于模板渲染
就比如,在寫到一個組件里面存著在不同邏輯的數據和處理數據的方法時,全部都混用了
<template><div class="hooks-test"><h2>當前求和為:{{sum}}</h2><button @click="add">點我+1</button><hr><img v-for="(dog,index) in dogList" :src="dog" :key="index"><br><button @click="getDog">再來一只狗</button></div>
</template><script lang="ts" setup>import { ref,reactive } from 'vue'import axios from 'axios' let sum = ref(0)let dogList = reactive(['https:\/\/images.dog.ceo\/breeds\/pembroke\/n02113023_2390.jpg'])function add(){sum.value += 1}// function getDog(){// axios.get('https://dog.ceo/api/breeds/image/random')// .then(res => {// dogList.push(res.data.message)// })// }async function getDog(){try {let result = await axios.get('https://dog.ceo/api/breeds/image/random')dogList.push(result.data.message)} catch (error) {alert(error)}}</script><style scoped>
.hooks-test{background-color: beige;padding: 20px;text-align: center;border: 1px solid black;
}
img{height: 200px;margin: 20px;
}
</style>
?圖示代碼又處理數字數據又處理狗狗數據,數據和處理邏輯沒有分開,導致代碼閱讀時有一定的障礙,這個時候我們就可以使用hooks自定義,將實現同一個功能所需要的數據和方法單獨封裝在一起
Hook 基礎結構
首先注意,hook命名規范是useXxxx.js/ts
1. 計數器 Hook 示例 (useCounter.ts
)
import { ref, onMounted } from 'vue'export default function(initialValue = 0) {// 響應式數據const count = ref(initialValue)// 操作方法const increment = () => count.value++const decrement = () => count.value--const reset = () => count.value = initialValue// 生命周期onMounted(() => {console.log('計數器Hook已掛載')})// 暴露給組件的APIreturn {count,increment,decrement,reset}
}
2. 異步數據 Hook 示例 (useFetch.ts
)
import { reactive, onMounted } from 'vue'
import axios from 'axios'export default function(url: string) {const state = reactive({data: null as any,loading: false,error: null as Error | null})const fetchData = async () => {state.loading = truetry {const response = await axios.get(url)state.data = response.data} catch (err) {state.error = err as Error} finally {state.loading = false}}onMounted(fetchData)return {...state,refetch: fetchData}
}
組件中使用 Hook
1. 基礎用法
<template><div><p>Count: {{ counter.count }}</p><button @click="counter.increment">+</button><button @click="counter.decrement">-</button><button @click="counter.reset">Reset</button></div>
</template><script setup lang="ts">
import useCounter from '@/hooks/useCounter'const counter = useCounter(10) // 初始值10
</script>
2. 組合多個 Hook
<template><div><!-- 計數器 --><div>{{ counter.count }}</div><!-- 異步數據 --><div v-if="posts.loading">Loading...</div><div v-else>{{ posts.data }}</div></div>
</template><script setup lang="ts">
import useCounter from '@/hooks/useCounter'
import useFetch from '@/hooks/useFetch'const counter = useCounter()
const posts = useFetch('/api/posts')
</script>
最佳實踐
1. 命名規范
- 始終以
use
開頭 (useXxx
) - 一個文件只包含一個 Hook
- 放在
src/hooks
目錄下
2. 類型安全
// useUser.ts
import { ref } from 'vue'interface User {id: numbername: stringemail: string
}export default function() {const user = ref<User | null>(null)return {user}
}
3. 復雜 Hook 示例 (usePagination.ts
)
import { reactive, computed } from 'vue'export default function(totalItems: number, perPage = 10) {const state = reactive({currentPage: 1,perPage})const totalPages = computed(() => Math.ceil(totalItems / state.perPage))const nextPage = () => {if (state.currentPage < totalPages.value) {state.currentPage++}}return {...state,totalPages,nextPage}
}
與 Vue 2 Mixins 對比
特性 | 自定義 Hook | Mixins |
---|---|---|
?代碼組織? | 按功能組織 | 按選項合并 |
?命名沖突? | 無(可重命名解構) | 容易沖突 |
?類型支持? | 完整 TypeScript 支持 | 支持有限 |
?邏輯復用? | 顯式導入 | 全局混入 |
?調試? | 清晰的調用棧 | 難以追蹤邏輯來源 |
高級技巧
1. 本地存儲 Hook (useLocalStorage.ts
)
import { ref, watch } from 'vue'export default function(key: string, defaultValue: any) {const data = ref(JSON.parse(localStorage.getItem(key) || 'null') || defaultValue)watch(data, (newVal) => {localStorage.setItem(key, JSON.stringify(newVal))}, { deep: true })return data
}
2. 鼠標位置 Hook (useMouse.ts
)
import { ref, onMounted, onUnmounted } from 'vue'export default function() {const x = ref(0)const y = ref(0)const update = (e: MouseEvent) => {x.value = e.pageXy.value = e.pageY}onMounted(() => window.addEventListener('mousemove', update))onUnmounted(() => window.removeEventListener('mousemove', update))return { x, y }
}
常見問題解決
1. Hook 之間共享狀態
// useSharedState.ts
import { reactive } from 'vue'const state = reactive({count: 0
})export function useSharedCounter() {const increment = () => state.count++return {count: state.count,increment}
}
2. 響應式解構問題
// ? 錯誤 - 失去響應性
const { count, increment } = useCounter()// ? 正確 - 保持響應性
const counter = useCounter()
// 在模板中使用 counter.count
(未完)?