目錄
1.Vue3 響應式原理
一、?響應式的基本概念
二、 核心機制:Proxy 和依賴追蹤
三、 觸發更新的過程
四、 代碼示例
五、 優勢總結
2.如何實現組件間通信?
一、父子組件通信
1. 父傳子:Props 傳遞
2. 子傳父:自定義事件
二、兄弟組件通信
1. 通過共同父組件中轉
2. 事件總線(Event Bus)
三、跨層級通信
1. Provide/Inject(依賴注入)
2. 全局狀態管理(Vuex/Pinia)
四、特殊場景方案
1.$refs 直接訪問(慎用)
2.$attrs/$listeners(透傳特性)
五、通信方式對比
六、實際案例參考:
3.Composition 的生命周期鉤子
一、主要生命周期鉤子函數
1.onBeforeMount()
2.onMounted()
3.onBeforeUpdate()
4.onUpdated()
5.onBeforeUnmount()
6.onUnmounted()
7.onErrorCaptured()
二、使用注意事項
三、代碼示例
4.Composition API vs Options API
一、Options API(選項式 API)
1. 特點:
2. 示例代碼:
3.優點:
4.缺點:
二、?Composition API(組合式 API)
1. 特點:
2. 示例代碼:
3. 優點:
4. 缺點:
三、核心對比
四、使用場景:
5.setup()?函數作用
一、?核心作用
二、關鍵注意事項
6.ref 和 reactive 的區別
一、基本定義
二、主要區別
三、示例
四、使用場景
7.Props 傳遞機制
一、基本概念
二、?在子組件中聲明 Props
聲明方式:
Options API:
Composition API:
三、在父組件中傳遞 Props
示例:
四、?Props 的類型驗證和默認值
示例:
五、單向數據流原則
六、高級用法
8.自定義事件 (emit)
一、在子組件中定義和觸發事件
二、 在父組件中監聽事件
三、注意事項
9.生命周期鉤子對比
一、生命周期階段與鉤子對照表
二、關鍵變化說明
1.重命名的鉤子
2.Composition API 特性
3.新增調試鉤子
三、執行順序對比(同一組件)
10.watch 和 watchEffect 的區別?
一、 基本概念
二、主要區別
三、示例
四、適用場景總結
1.watch?
2.watchEffect?
1.Vue3 響應式原理
Vue3 的響應式原理是其核心特性之一,它允許數據變化時自動更新視圖。相比 Vue2,Vue3 使用了 JavaScript 的 Proxy 對象來實現更高效和靈活的響應式系統。下面我將逐步解釋其工作機制,幫助你理解整個過程。
一、?響應式的基本概念
- 響應式系統確保當數據(如變量或對象屬性)發生變化時,依賴該數據的視圖或計算邏輯自動更新。這類似于數學中的函數依賴關系:如果 $y = f(x)$,那么當 $x$ 改變時,$y$ 應自動重新計算。
- Vue3 的核心是創建一個響應式代理對象,它會攔截對數據的訪問(get)和修改(set)操作,從而追蹤依賴并觸發更新。
二、 核心機制:Proxy 和依賴追蹤
- Proxy 對象:Vue3 使用 JavaScript 的 Proxy API 來包裝原始數據。Proxy 可以定義“陷阱”(traps),如
get
和set
,用于攔截操作。- 當訪問屬性時(如
obj.a
),get
陷阱被觸發,系統記錄當前依賴(例如一個渲染函數)。 - 當修改屬性時(如
obj.a = 1
),set
陷阱被觸發,系統通知所有依賴進行更新。
- 當訪問屬性時(如
- 依賴追蹤:Vue3 通過一個全局的“依賴收集器”來管理依賴關系。每個響應式屬性都關聯一個依賴集合(稱為
Dep
),當屬性被訪問時,當前運行的“effect”函數(如組件的渲染函數)會被添加到這個集合中。- 這可以用一個簡單的數學關系表示:假設有一個響應式對象 $data$,其屬性 $x$ 的依賴集合為 $D_x$。當 $x$ 改變時,系統遍歷 $D_x$ 并執行每個 effect 函數。
- 公式表示:如果 $effect \in D_x$,那么 $x$ 變化時 $effect()$ 被調用。
三、 觸發更新的過程
- 當數據被修改時,Proxy 的
set
陷阱會執行以下步驟:
- 更新原始數據值。
- 通知依賴集合:遍歷所有依賴的 effect 函數,并調用它們。
- 如果 effect 函數涉及計算屬性(如 $computed = x + y$),系統會重新計算這些值。
- 優勢:Proxy 支持深層嵌套對象和數組的響應式,無需像 Vue2 那樣遞歸遍歷整個對象,這提高了性能。
四、 代碼示例
下面是一個簡化版的 Vue3 響應式實現,使用 JavaScript 代碼演示核心邏輯。注意,實際 Vue3 源碼更復雜,但這里聚焦基本原理。
// 創建一個響應式對象
function reactive(target) {return new Proxy(target, {get(obj, key) {track(obj, key); // 追蹤依賴:記錄當前 effect 函數return obj[key];},set(obj, key, value) {obj[key] = value;trigger(obj, key); // 觸發更新:通知所有依賴return true;}});
}// 依賴收集和觸發函數(簡化版)
const depsMap = new Map(); // 存儲每個對象的依賴映射function track(target, key) {let dep = depsMap.get(target);if (!dep) {dep = new Map();depsMap.set(target, dep);}let effects = dep.get(key);if (!effects) {effects = new Set();dep.set(key, effects);}if (currentEffect) {effects.add(currentEffect); // 添加當前 effect 到依賴集合}
}function trigger(target, key) {const dep = depsMap.get(target);if (dep) {const effects = dep.get(key);if (effects) {effects.forEach(effect => effect()); // 執行所有依賴的 effect 函數}}
}// 定義一個 effect 函數(例如組件的渲染邏輯)
let currentEffect = null;
function effect(fn) {currentEffect = fn;fn(); // 首次執行,觸發依賴收集currentEffect = null;
}// 使用示例
const state = reactive({ count: 0 });effect(() => {console.log(`Count is: ${state.count}`); // 依賴 state.count
});state.count = 1; // 輸出:Count is: 1(自動觸發更新)
五、 優勢總結
- 高效性:Proxy 直接攔截操作,避免了 Vue2 中遞歸定義 getter/setter 的開銷,支持動態添加屬性。
- 靈活性:可以處理數組索引變化、Map/Set 等復雜數據結構。
- 可組合性:Vue3 的
reactive
和ref
API 使狀態管理更模塊化。 - 數學上,這類似于一個動態的依賴圖:每個響應式屬性是一個節點,依賴關系是邊,變化時沿邊傳播更新。
2.如何實現組件間通信?
實現組件間通信是前端開發中的核心問題,不同框架有各自的解決方案。以下是主流的通信方式及代碼示例:
一、父子組件通信
1. 父傳子:Props 傳遞
// 父組件
<Child :message="parentData" />// 子組件
export default {props: ['message'],mounted() {console.log(this.message) // 接收父組件數據}
}
2. 子傳父:自定義事件
// 子組件
this.$emit('child-event', data) // 觸發事件// 父組件
<Child @child-event="handleEvent" />methods: {handleEvent(data) {console.log(data) // 接收子組件數據}
}
二、兄弟組件通信
1. 通過共同父組件中轉
// 父組件
<ChildA @send="receiveData" />
<ChildB :data="sharedData" />data() {return { sharedData: null }
},
methods: {receiveData(data) {this.sharedData = data // 中轉數據}
}
2. 事件總線(Event Bus)
// 創建事件中心
const bus = new Vue()// 組件A(發送)
bus.$emit('update', data)// 組件B(接收)
bus.$on('update', data => {console.log(data)
})
三、跨層級通信
1. Provide/Inject(依賴注入)
// 祖先組件
provide() {return { theme: 'dark' }
}// 后代組件(任意層級)
inject: ['theme'],
mounted() {console.log(this.theme) // 輸出 'dark'
}
2. 全局狀態管理(Vuex/Pinia)
// store.js
export default new Vuex.Store({state: { count: 0 },mutations: {increment(state) {state.count++}}
})// 任意組件
this.$store.commit('increment')
console.log(this.$store.state.count)
四、特殊場景方案
1.$refs 直接訪問(慎用)
<Child ref="childComp" />methods: {callChild() {this.$refs.childComp.childMethod()}
}
2.$attrs/$listeners(透傳特性)
// 父組件
<Child :title="pageTitle" @close="handleClose" />// 中間組件
<Grandchild v-bind="$attrs" v-on="$listeners" />
五、通信方式對比
方式 | 適用場景 | 優點 | 缺點 |
---|---|---|---|
Props/Events | 父子組件 | 簡單直接 | 層級深時繁瑣 |
Event Bus | 任意組件 | 解耦靈活 | 難以追蹤事件源 |
Vuex/Pinia | 中大型應用 | 集中管理,調試工具完善 | 增加項目復雜度 |
Provide/Inject | 深層嵌套組件 | 避免逐層傳遞 | 數據非響應式(需處理) |
最佳實踐建議:
- 優先使用 Props/Events 處理父子通信
- 跨層級使用 Provide/Inject 替代多級 Props
- 復雜應用采用 Vuex/Pinia 管理全局狀態
- 避免過度使用 $refs 和事件總線,防止代碼混亂
六、實際案例參考:
// 使用Pinia狀態管理
// store/counter.js
export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),actions: {increment() {this.count++}}
})// 組件A
import { useCounterStore } from '@/store/counter'
const store = useCounterStore()
store.increment() // 組件B
const store = useCounterStore()
console.log(store.count) // 實時獲取最新值
3.Composition 的生命周期鉤子
在 Vue.js 的 Composition API 中,生命周期鉤子提供了一種在組件不同階段(如創建、更新、銷毀)執行自定義邏輯的方式。與 Options API 不同,Composition API 使用函數式鉤子(如 onMounted
、onUnmounted
),這些鉤子需要在 setup()
函數或 <script setup>
語法中導入和使用。下面我將逐步解釋主要鉤子及其用法。
一、主要生命周期鉤子函數
Composition API 提供了以下核心鉤子,每個鉤子對應組件生命周期的特定階段:
1.onBeforeMount()
- 用途:在組件掛載到 DOM 之前調用。適合執行初始化操作,如設置狀態或獲取數據。
- 觸發時機:在
setup()
函數運行后,組件首次渲染前。
2.onMounted()
- 用途:在組件掛載到 DOM 后調用。常用于訪問 DOM 元素、發起 API 請求或設置事件監聽器。
- 觸發時機:組件首次渲染完成。
3.onBeforeUpdate()
- 用途:在組件更新之前調用。適合在狀態變化前執行清理或驗證邏輯。
- 觸發時機:響應式數據變化后,DOM 更新前。
4.onUpdated()
- 用途:在組件更新后調用。用于處理更新后的 DOM 操作或狀態同步。
- 觸發時機:DOM 重新渲染完成后。
5.onBeforeUnmount()
- 用途:在組件卸載之前調用。適合執行清理工作,如移除事件監聽器或取消定時器。
- 觸發時機:組件銷毀流程開始前。
6.onUnmounted()
- 用途:在組件卸載后調用。用于最終資源釋放,如斷開網絡連接或清除緩存。
- 觸發時機:組件從 DOM 中移除后。
7.onErrorCaptured()
- 用途:捕獲子組件或當前組件的錯誤。用于錯誤處理或日志記錄。
- 觸發時機:組件樹中任何地方拋出錯誤時。
二、使用注意事項
- 執行順序:鉤子按生命周期順序執行,例如:
onBeforeMount
→onMounted
→onBeforeUpdate
→onUpdated
→onBeforeUnmount
→onUnmounted
。 - 依賴導入:所有鉤子需從
vue
包導入,并在setup()
函數內調用。 - 異步支持:鉤子回調可以是異步函數,適用于數據獲取等操作。
- 性能優化:避免在頻繁更新的鉤子(如
onUpdated
)中執行重操作,以防止性能問題。
三、代碼示例
以下是一個簡單的 Vue 3 組件示例,展示如何使用 Composition API 的生命周期鉤子:
<template><div>{{ message }}</div>
</template><script>
import { ref, onMounted, onUnmounted } from 'vue';export default {setup() {const message = ref('組件加載中...');// 掛載后更新消息onMounted(() => {message.value = '組件已掛載!';console.log('DOM 已渲染');});// 卸載時清理資源onUnmounted(() => {console.log('組件已卸載');});return { message };}
};
</script>
4.Composition API vs Options API
在 Vue.js 開發中,Composition API 和 Options API 是兩種不同的組件代碼組織方式。以下是它們的核心區別和適用場景分析:
一、Options API(選項式 API)
1. 特點:
- 通過預設選項(如
data
,methods
,computed
)組織代碼 - 邏輯分散在不同選項中,相同功能可能跨多個選項
- 適合簡單場景,學習曲線平緩
2. 示例代碼:
export default {data() {return { count: 0 }},methods: {increment() {this.count++}},computed: {doubleCount() {return this.count * 2}}
}
3.優點:
- 結構清晰直觀,適合新手
- 選項隔離降低耦合度
- 兼容性好(Vue 2/3 均支持)
4.缺點:
- 復雜組件中邏輯碎片化
- 代碼復用依賴 mixins(易命名沖突)
二、?Composition API(組合式 API)
1. 特點:
- 通過
setup()
函數集中管理邏輯 - 基于函數組合(如
ref
,reactive
,computed
) - 邏輯按功能聚合,而非選項類型
2. 示例代碼:
import { ref, computed } from 'vue'export default {setup() {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return { count, doubleCount, increment }}
}
3. 優點:
- 邏輯高內聚,復雜組件更易維護
- 更好的 TypeScript 支持
- 靈活的邏輯復用(自定義 Hook)
- 代碼更精簡(減少
this
依賴)
4. 缺點:
- 學習曲線較陡峭(需理解響應式原理)
- 過度集中可能降低可讀性
三、核心對比
維度 | Options API | Composition API |
---|---|---|
代碼組織 | 按選項類型分散 | 按功能邏輯集中 |
邏輯復用 | Mixins(易沖突) | 自定義 Hook(解耦性強) |
TS 支持 | 有限 | 完整類型推斷 |
適用場景 | 簡單組件/新手項目 | 復雜邏輯/大型應用 |
響應式數據 | 通過 data() 返回 | 通過 ref() /reactive() 聲明 |
四、使用場景:
Options API :項目簡單或團隊 Vue 經驗較少,需要快速迭代原型,維護舊版 Vue 2 項目
Composition API:組件邏輯復雜(如狀態管理、異步流程),需要高度復用邏輯(自定義 Hook),使用 TypeScript 開發,長期維護的大型項目
5.setup()
?函數作用
一、?核心作用
- setup()函數主要用于初始化程序環境,包括設置變量初始值、配置硬件參數、定義畫布大小等。
- 它只在程序執行時調用一次,之后不再運行
二、關鍵注意事項
- 執行時機:setup()只在程序啟動時運行一次
- 必要性:在支持setup()的框架中,它是必須定義的函數(即使為空),否則程序可能報錯
- 常見錯誤:如果在setup()外部放置初始化代碼,可能導致未定義行為或性能問題
6.ref
和 reactive
的區別
一、基本定義
ref
: 用于創建一個響應式引用,適用于基本類型(如數字、字符串、布爾值)或對象。它返回一個帶有.value
屬性的對象,訪問或修改數據需要通過.value
。reactive
: 用于創建一個響應式對象,適用于對象或數組。它直接返回一個代理對象,屬性可以直接訪問和修改,無需額外語法。
二、主要區別
方面 | ref | reactive |
---|---|---|
適用類型 | 更適合基本類型(如 number , string ),也可用于對象。 | 僅適用于對象或數組,不適用于基本類型。 |
訪問方式 | 必須通過 .value 訪問或修改數據(例如 myRef.value )。 | 直接訪問屬性(例如 myReactive.key ),無需 .value 。 |
模板使用 | 在模板中自動解包,無需寫 .value (Vue 內部處理)。 | 在模板中直接使用屬性名,行為更直觀。 |
內部實現 | 包裝一個值,使用 Object.defineProperty 或 Proxy 實現響應式。 | 基于 Proxy 代理整個對象,深度監聽嵌套屬性。 |
解構問題 | 解構后仍保留響應性(因為返回的是引用對象)。 | 解構對象會丟失響應性,需使用 toRefs 輔助函數保持。 |
重新賦值 | 可以重新賦值整個對象(通過 myRef.value = newValue )。 | 不能直接重新賦值整個對象;需修改屬性或使用 Object.assign 。 |
三、示例
import { ref, reactive } from 'vue';// 使用 ref 示例
const countRef = ref(0); // 基本類型
console.log(countRef.value); // 輸出: 0
countRef.value = 10; // 修改值const userRef = ref({ name: 'Alice', age: 30 }); // 對象類型
console.log(userRef.value.name); // 輸出: 'Alice'// 使用 reactive 示例
const userReactive = reactive({ name: 'Bob', age: 25 }); // 對象類型
console.log(userReactive.name); // 輸出: 'Bob',無需 .value
userReactive.age = 26; // 直接修改屬性// 解構問題演示
const { age } = userReactive; // 解構會丟失響應性
// 正確方式:使用 toRefs 保持響應性
const { name, age: reactiveAge } = toRefs(userReactive);
四、使用場景
ref:
處理基本類型數據,需要頻繁重新賦值整個對象?
reactive
:處理復雜對象或嵌套數據結構,需要直接訪問屬性,避免寫 .value
的模板代碼
總結:
- 用
reactive
管理狀態對象,用ref
處理獨立的基本值 - 選擇時考慮數據結構和代碼可讀性:簡單值用
ref
,復雜對象用reactive
。
7.Props 傳遞機制
一、基本概念
- Props 允許父組件將數據“注入”到子組件中,子組件通過聲明 Props 來接收這些數據。
- 數據流是單向的:父組件更新 Props 會觸發子組件重新渲染,但子組件不能直接修改 Props
- 用途:適用于配置子組件、傳遞靜態或動態數據,例如傳遞用戶信息、配置選項等。
二、?在子組件中聲明 Props
子組件需要顯式聲明它可以接收的 Props。這通常在組件的選項或 Composition API 中完成。
-
聲明方式:
- 使用
props
選項(Options API)或defineProps
宏(Composition API)。 - 每個 Prop 可以指定類型、默認值和驗證規則。
- 使用
-
Options API:
// 子組件 (ChildComponent.vue) export default {props: {// 基本類型聲明,例如字符串title: String,// 帶默認值的數字類型count: {type: Number,default: 0},// 必填的布爾類型isActive: {type: Boolean,required: true}} }
Composition API:
// 子組件 (ChildComponent.vue) import { defineProps } from 'vue'const props = defineProps({title: String,count: { type: Number, default: 0 },isActive: { type: Boolean, required: true } })
三、在父組件中傳遞 Props
- 靜態傳遞:直接傳遞固定值。
- 動態傳遞:綁定到父組件的數據或計算屬性,實現響應式更新。
示例:
<!-- 父組件模板 -->
<template><ChildComponenttitle="歡迎使用 Vue 3" <!-- 靜態字符串 -->:count="parentCount" <!-- 動態綁定數字 -->:is-active="isActive" <!-- 動態綁定布爾值 -->/>
</template><script>
import ChildComponent from './ChildComponent.vue'export default {components: { ChildComponent },data() {return {parentCount: 10, // 父組件數據isActive: true}}
}
</script>
四、?Props 的類型驗證和默認值
- 類型:可以指定為原生類型(如
String
,Number
,Boolean
,Array
,Object
)或自定義類型。 - 默認值:通過
default
屬性設置,當父組件未傳遞 Prop 時使用。 - 驗證:使用
validator
函數進行自定義驗證
示例:
props: {age: {type: Number,default: 18,validator: (value) => value >= 0 // 驗證年齡非負}
}
五、單向數據流原則
- Props 是只讀的:子組件不能直接修改接收到的 Prop。如果需要基于 Prop 派生數據,應使用計算屬性。
- 原因:確保數據源單一,避免父子組件間的循環更新。
// 子組件中錯誤做法:直接修改 Prop // this.count = 20 // 不允許,會觸發警告// 正確做法:使用計算屬性或本地數據 computed: {doubledCount() {return this.count * 2 // 基于 Prop 派生新值} }
六、高級用法
- 傳遞對象或數組:使用
v-bind
傳遞整個對象,子組件通過 Prop 接收。<!-- 父組件 --> <ChildComponent :user-info="{ name: '張三', age: 30 }" />
- Prop 命名約定:建議使用 camelCase 聲明,但在模板中使用 kebab-case(HTML 屬性不區分大小寫)。例如,聲明為
userInfo
,傳遞時用user-info
。 - 響應式更新:父組件數據變化時,子組件的 Prop 會自動更新(得益于 Vue 的響應式系統)。
8.自定義事件 (emit
)
一、在子組件中定義和觸發事件
- 使用
defineEmits
聲明事件列表。 - 在方法中調用
emit
函數觸發事件,并傳遞數據。 - 在模板中綁定事件觸發器(如按鈕點擊)。
//子組件 ChildComponent.vue
<script setup>
// 導入 defineEmits
import { defineEmits } from 'vue';// 定義事件列表:聲明一個名為 'customEvent' 的事件
const emit = defineEmits(['customEvent']);// 定義一個方法,在觸發時發送事件
function handleClick() {// 觸發 'customEvent' 事件,并傳遞數據(例如字符串 'Hello from child!')emit('customEvent', 'Hello from child!');
}
</script><template><!-- 在按鈕點擊時調用 handleClick 方法 --><button @click="handleClick">觸發自定義事件</button>
</template>
二、 在父組件中監聽事件
- 導入子組件。
- 在模板中使用
@event-name
或v-on:event-name
監聽事件。 - 定義一個回調函數來處理事件數據。
//父組件 ParentComponent.vue
<script setup>
// 導入子組件
import ChildComponent from './ChildComponent.vue';// 定義回調函數,接收子組件傳遞的數據
function onCustomEvent(data) {console.log('事件觸發!數據:', data); // 輸出:事件觸發!數據: Hello from child!// 這里可以添加業務邏輯,例如更新父組件狀態
}
</script><template><!-- 監聽子組件的 'customEvent' 事件,并綁定回調函數 --><ChildComponent @customEvent="onCustomEvent" />
</template>
三、注意事項
- 事件命名規范:推薦使用 kebab-case(短橫線分隔)命名事件,如
custom-event
,以保持與 HTML 屬性一致。在模板中監聽時使用@custom-event
,但在defineEmits
中聲明時使用 camelCase(如customEvent
)。 - 數據傳遞:
emit
可以傳遞多個參數,例如emit('event', arg1, arg2)
,父組件回調函數接收這些參數。 - TypeScript 支持:如果使用 TypeScript,可以通過泛型定義事件類型:
<script setup lang="ts"> const emit = defineEmits<{(event: 'customEvent', data: string): void; }>(); </script>
- 錯誤處理:確保事件名一致,避免拼寫錯誤。如果父組件未監聽事件,子組件的
emit
不會報錯,但也不會執行任何操作。 - 替代方案:對于簡單場景,也可以使用 Props 傳遞數據,但自定義事件更適合子組件主動通知父組件的場景。
9.生命周期鉤子對比
Vue 3 的生命周期鉤子在 Options API 和 Composition API 中有不同實現方式,同時部分鉤子名稱與 Vue 2 有差異。以下是核心對比:
一、生命周期階段與鉤子對照表
階段 | Vue 2 (Options) | Vue 3 (Options API) | Vue 3 (Composition API) |
---|---|---|---|
初始化 | beforeCreate | beforeCreate | 無(邏輯在 setup() 內) |
created | created | 無(邏輯在 setup() 內) | |
掛載前 | beforeMount | beforeMount | onBeforeMount |
掛載完成 | mounted | mounted | onMounted |
更新前 | beforeUpdate | beforeUpdate | onBeforeUpdate |
更新完成 | updated | updated | onUpdated |
卸載前 | beforeDestroy | beforeUnmount | onBeforeUnmount |
卸載完成 | destroyed | unmounted | onUnmounted |
緩存組件 | activated | activated | onActivated |
deactivated | deactivated | onDeactivated | |
錯誤捕獲 | errorCaptured | errorCaptured | onErrorCaptured |
調試鉤子 | 無 | 無 | onRenderTracked |
onRenderTriggered |
二、關鍵變化說明
1.重命名的鉤子
beforeDestroy
→beforeUnmount
(更準確描述組件卸載行為)destroyed
→unmounted
(語義更清晰)
2.Composition API 特性
- 所有鉤子以
onXxx
形式導入(如onMounted
) beforeCreate
和created
被setup()
替代:import { onMounted } from 'vue';export default {setup() {// 替代 created 邏輯console.log("初始化邏輯");onMounted(() => {console.log("組件已掛載");});} }
3.新增調試鉤子
onRenderTracked
:追蹤響應式依賴onRenderTriggered
:診斷重新渲染原因
三、執行順序對比(同一組件)
Options API: beforeCreate → created → beforeMount → mounted → beforeUpdate → updated → beforeUnmount → unmountedComposition API:setup() → onBeforeMount → onMounted → onBeforeUpdate → onUpdated → onBeforeUnmount → onUnmounted
最佳實踐建議:
- 新項目優先使用 Composition API,邏輯更聚合
- 遷移項目可逐步替換重命名鉤子(如
beforeDestroy
→beforeUnmount
)- 調試響應式問題時使用
onRenderTracked
/onRenderTriggered
10.watch
和 watchEffect
的區別?
一、 基本概念
watch
: 用于顯式觀察一個或多個響應式數據源(如 ref、reactive 對象、函數等),并在其變化時執行回調函數。它允許你指定依賴項,并控制監聽行為(如深度監聽或立即執行)。watchEffect
: 自動跟蹤其函數體內部的響應式依賴項,并在任何依賴變化時重新運行該函數。它類似于計算屬性(computed),但用于執行副作用(如 DOM 操作、API 調用),而非返回一個值。
二、主要區別
特性 | watch | watchEffect |
---|---|---|
依賴項指定方式 | 需要顯式聲明依賴源(如 () => data.value )。 | 自動收集函數體中的所有響應式依賴項。 |
初始執行行為 | 默認不立即執行回調;可通過 { immediate: true } 選項啟用。 | 在創建時立即執行一次函數體。 |
訪問舊值/新值 | 回調函數可接收舊值和新值作為參數(如 (newVal, oldVal) => {} )。 | 無法直接訪問舊值;只提供當前值。 |
深度監聽支持 | 支持 { deep: true } 選項進行深度監聽(如對象嵌套屬性)。 | 默認深度監聽所有依賴項,無需額外選項。 |
適用場景 | 適合精確控制監聽邏輯,如當特定數據變化時觸發操作。 | 適合自動響應依賴變化,如執行副作用或初始化任務。 |
停止監聽 | 通過返回的停止函數手動停止(如 const stop = watch(...); stop() )。 | 同樣通過返回的停止函數手動停止。 |
三、示例
import { ref, reactive, watch, watchEffect } from 'vue';export default {setup() {const count = ref(0);const user = reactive({ name: 'Alice', age: 25 });// 示例 1: 使用 watchwatch(// 顯式指定依賴源:count 和 user.name[() => count.value, () => user.name],// 回調函數接收新值和舊值([newCount, newName], [oldCount, oldName]) => {console.log('watch - count 變化:', newCount, '舊值:', oldCount);console.log('watch - name 變化:', newName, '舊值:', oldName);},// 選項:立即執行和深度監聽{ immediate: true, deep: true });// 示例 2: 使用 watchEffectwatchEffect(() => {// 自動跟蹤 count.value 和 user.ageconsole.log('watchEffect - count:', count.value);console.log('watchEffect - age:', user.age);// 注意:這里無法訪問舊值});// 修改數據以觸發監聽setTimeout(() => {count.value = 1; // 觸發 watch 和 watchEffectuser.name = 'Bob'; // 觸發 watchuser.age = 26; // 觸發 watchEffect}, 1000);return { count, user };}
};
四、適用場景總結
1.watch
?
- 你需要精確控制監聽哪些數據源。
- 需要訪問變化前后的值(如比較舊值和新值)。
- 場景示例:表單驗證(當特定字段變化時檢查)、API 請求(當 ID 變化時重新獲取數據)。
2.watchEffect
?
- 依賴項復雜或動態,希望自動跟蹤所有響應式引用。
- 需要立即執行副作用(如初始化日志或設置事件監聽器)。
- 場景示例:實時日志輸出、自動清理資源(結合
onInvalidate
函數)。
如果依賴項明確且需要舊值,用
watch
;如果依賴項動態或需要簡化代碼,用watchEffect
。