組合式 API是vue3 的核心特性,替代 Vue2 的選項式 API,強調邏輯復用和代碼組織。基本語法如下:
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';// 1. 響應式數據
const count = ref(0); // 基本類型用 ref
const user = reactive({ // 對象類型用 reactivename: 'Alice',age: 25
});// 2. 計算屬性
const isAdult = computed(() => user.age >= 18);// 3. 方法
const increment = () => {count.value++;
};// 4. 生命周期鉤子
onMounted(() => {console.log('組件已掛載');
});
</script><template><div><p>{{ count }}</p><button @click="increment">+1</button><p>{{ user.name }} 是否成年:{{ isAdult ? '是' : '否' }}</p></div>
</template>
一、響應式系統
1、ref
?和?reactive
(1) 數據類型
ref
-
適用于基本類型(如?
number
、string
、boolean
)。 -
也可以用于對象或數組(內部會調用?
reactive
?處理)。 -
通過?
.value
?訪問數據。
reactive
-
僅適用于對象或數組。
-
直接訪問屬性,無需?
.value
。
(2) 使用場景
-
ref
-
需要處理基本類型。
-
需要將數據傳遞到其他函數或組件時(因?
ref
?是對象,可以保持引用)。
-
-
reactive
-
需要處理復雜對象或數組。
-
需要直接操作嵌套屬性(如?
state.user.name
)。
-
2、?toRef
?和?toRefs
-
toRef
-
功能:將響應式對象(
reactive
?生成)的某個屬性轉換為一個?ref
,并保持響應式連接。 -
特點:
-
生成的?
ref
?與原對象的屬性同步更新(修改?ref
?會影響原對象,反之亦然)。 -
如果原對象屬性是非響應式的,
toRef
?不會使其變為響應式。
-
-
import { reactive, toRef } from 'vue';const state = reactive({ count: 0 });
const countRef = toRef(state, 'count');// 修改 ref 會更新原對象
countRef.value++;
console.log(state.count); // 1// 修改原對象也會更新 ref
state.count++;
console.log(countRef.value); // 2
toRef
?的典型場景
①解構響應式對象時保持響應性
直接解構?reactive
?對象會丟失響應性,但用?toRef
?可以解決:
const state = reactive({ count: 0 });// ? 錯誤:解構會丟失響應性
const { count } = state;// ? 正確:使用 toRef 保持響應性
const countRef = toRef(state, 'count');
②將?props
?的某個屬性轉為?ref
在組合式函數中,直接解構?props
?會失去響應性,此時可以用?toRef
:
<script setup>
const props = defineProps(['userId']);
const userIdRef = toRef(props, 'userId'); // 保持響應性
</script>
toRefs
-
toRef
:僅轉換對象的一個屬性為?ref
。 -
toRefs
:將整個響應式對象的所有屬性轉換為普通對象,每個屬性都是?ref
。
const state = reactive({ count: 0, name: 'A' });
const stateRefs = toRefs(state);
// { count: Ref<0>, name: Ref<'A'> }
console.log(stateRefs.count.value); // 0
3、 shallowRef
shallowRef只會在對象的第一層進行響應式處理,不會遞歸地對對象內部的屬性進行響應式處理。
import { shallowRef } from 'vue';const user = shallowRef({ name: 'John', address: { city: 'New York' } });
console.log(user.value); // 輸出: { name: 'John', address: { city: 'New York' } }
user.value.address.city = 'San Francisco'; // 修改深層屬性
console.log(user.value); // 輸出: { name: 'John', address: { city: 'San Francisco' } }
// 注意,修改深層屬性不會觸發視圖更新
4、computed
computed 用于創建計算屬性,它基于一個或多個響應式數據(如 ref 或 reactive)計算出一個新的值。計算屬性是惰性的,只有在被訪問時才會重新計算,并且會緩存結果以提高性能。
特點
- 依賴追蹤:自動追蹤依賴的響應式數據,僅在依賴變化時重新計算。
- 緩存機制:計算結果會被緩存,直到依賴數據變化才重新計算。
- 返回值:必須返回一個值,通常用于派生數據。
使用場景
- 需要根據響應式數據計算派生值。
- 在模板中多次使用某個計算結果,利用緩存提升性能。
- 將復雜邏輯封裝成一個屬性。
<script setup>
import { ref, computed } from 'vue';const firstName = ref('John');
const lastName = ref('Doe');const fullName = computed(() => `${firstName.value} ${lastName.value}`);
</script><template><p>{{ fullName }}</p> <!-- 輸出 'John Doe',依賴變化時自動更新 -->
</template>
5、watch
watch 用于監聽指定的響應式數據(如 ref、reactive 或計算屬性)的變化,并在數據變化時執行回調函數。
特點
- 手動指定依賴:需要明確指定監聽的數據源。
- 支持深度監聽:通過 { deep: true } 選項可監聽對象或數組的深層變化。
- 新舊值:回調函數可接收新值和舊值,便于比較。
- 多數據監聽:可通過數組或對象同時監聽多個數據源。
使用場景
- 數據變化時需要執行副作用,如發送請求、更新 DOM。
- 需要監聽深層對象或數組變化。
- 需要執行異步操作或依賴新舊值比較。
6、watchEffect
watchEffect 是一個簡化的監聽工具,它會自動追蹤回調函數中使用的響應式依賴,并在依賴變化時重新運行回調,無需手動指定監聽目標。
特點
- 自動依賴追蹤:Vue 自動檢測回調中使用的響應式數據。
- 立即執行:創建時立即運行一次以收集依賴。
- 不支持新舊值:回調函數不接收新舊值參數。
- 副作用優先:適合執行不需要返回值的操作。
使用場景
- 多個響應式數據變化時執行相同副作用。
- 依賴關系動態變化,不想手動管理。
- 簡單的副作用邏輯,如日志記錄或 DOM 操作。
二、Class 與 Style 綁定
1. Class 綁定
Class 綁定讓你可以動態切換 CSS 類。Vue 3 提供了多種語法來實現這一點:
2.1.1. 對象語法
可以用一個對象來綁定 Class,對象的鍵是 CSS 類名,值是布爾值,表示是否應用該類。
<template><div :class="{ active: isActive, 'text-danger': hasError }"></div>
</template><script setup>
import { ref } from 'vue';const isActive = ref(true);
const hasError = ref(false);
</script>
解釋:
- :class 是 v-bind:class 的縮寫,用于動態綁定。
- 如果 isActive 為 true,<div> 會有 active 類。
- 如果 hasError 為 true,<div> 會有 text-danger 類。
2.1.2. 數組語法
也可以用數組列出要應用的類名
<template><div :class="[activeClass, errorClass, 'static-class']"></div>
</template><script setup>
import { ref } from 'vue';const activeClass = ref('active'); // 動態類名
const errorClass = ref(''); // 空類名不渲染
</script>
渲染結果:
<div class="active static-class"></div>
如果你也想在數組中有條件地渲染某個 class,你可以使用三元表達式:
<div :class="[isActive ? activeClass : '', errorClass]"></div>
解釋:
errorClass
?會一直存在,但?activeClass
?只會在?isActive
?為真時才存在。
2.1.3. 混合對象和數組
<template><div :class="[isActive ? 'active' : '', { 'text-danger': hasError }]"></div>
</template><script setup>
import { ref } from 'vue';const isActive = ref(true);
const hasError = ref(false);
</script>
解釋:
- isActive ? 'active' : '':如果 isActive 為 true,應用 active 類,否則應用空字符串。
- { 'text-danger': hasError }:如果 hasError 為 true,應用 text-danger 類。
2.1.4. 綁定組件根元素的 Class
在自定義組件上綁定 Class 時,類會應用到組件的根元素。
子組件?Child.vue
:
<template><div class="child-root"><!-- 父組件傳遞的 class 會合并到根元素 --></div>
</template>
父組件使用:
<template><Child class="parent-class" />
</template>
渲染結果:
<div class="child-root parent-class"></div>
2.style 綁定
Style 綁定用于動態設置內聯樣式,同樣支持對象和數組語法。
2.2.1. 對象語法
用對象綁定樣式,鍵是 CSS 屬性名,值是屬性值。
<template><div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
</template><script setup>
import { ref } from 'vue';const activeColor = ref('red');
const fontSize = ref(16);
</script>
解釋:
- :style 是 v-bind:style 的縮寫。
- color 被設置為 red。
- fontSize 被設置為 16px(注意單位需要手動拼接)。
- 屬性名可以用駝峰式(如 fontSize)或短橫線分隔(如 font-size),推薦駝峰式。
2.2.2. 數組語法
用數組應用多個樣式對象。
<template><div :style="[baseStyles, overridingStyles]"></div>
</template><script setup>
import { reactive } from 'vue';const baseStyles = reactive({color: 'blue',fontSize: '14px'
});const overridingStyles = reactive({fontSize: '18px'
});
</script>
解釋:
- baseStyles 設置 color: blue 和 fontSize: 14px。
- overridingStyles 設置 fontSize: 18px,會覆蓋前面的 fontSize。
- 最終結果:color: blue; font-size: 18px。
2.2.3. 自動前綴
Vue 3 會自動為某些樣式屬性添加瀏覽器前綴。例如:
<template><div :style="{ transform: 'rotate(45deg)' }"></div>
</template>
Vue 會自動生成 -webkit-transform 等前綴,確保兼容性。
2.2.4. 多值綁定(Vue 3.2+)
可以為單個屬性提供多個值,Vue 會應用最后一個支持的值。
<template><div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
</template>
3.使用計算屬性
對于復雜邏輯,可以用 computed 生成 Class 或 Style。
2.3.1. Class 綁定示例
<template><div :class="classes"></div>
</template><script setup>
import { computed, ref } from 'vue';const isActive = ref(true);
const hasError = ref(false);const classes = computed(() => ({active: isActive.value,'text-danger': hasError.value
}));
</script>
解釋:classes 根據 isActive 和 hasError 的值動態返回類對象。
2.3.2. Style 綁定示例
<template><div :style="styles"></div>
</template><script setup>
import { computed, ref } from 'vue';const color = ref('red');
const fontSize = ref(16);const styles = computed(() => ({color: color.value,fontSize: `${fontSize.value}px`
}));
</script>
解釋:styles 根據 color 和 fontSize 的值動態返回樣式對象。
4.注意事項
- 響應式:綁定的 Class 和 Style 會隨數據變化自動更新。
- 與靜態共存:靜態的 class 和 style 可以與動態綁定共用。
<template><div class="static" :class="{ active: isActive }" style="color: blue" :style="{ fontSize: fontSize + 'px' }"></div>
</template>
結果:<div> 始終有 static 類和 color: blue,同時根據 isActive 和 fontSize 動態應用其他值。
三、watch和watchEffect用法細說:
1、watch:
3.1.1.?核心特點
-
顯式指定監聽的數據源(需手動列出依賴)。
-
惰性執行:默認不會立即執行回調,除非設置?
immediate: true
。 -
適合場景:需要精確控制監聽目標和回調邏輯
3.1.2.?基本語法
import { watch, ref } from 'vue';const data = ref(value);watch(source, // 監聽的數據源(ref、reactive、getter 函數或數組)(newVal, oldVal) => { /* 回調邏輯 */ },{ immediate: false, // 是否立即執行回調deep: false // 是否深度監聽對象/數組}
);
3.1.3.?代碼示例
①監聽單個數據源
<script setup>
import { ref, watch } from 'vue';const count = ref(0);// 監聽 count 的變化
watch(count, (newVal, oldVal) => {console.log(`count 從 ${oldVal} 變為 ${newVal}`);
});const increment = () => {count.value++;
};
</script><template><button @click="increment">+1</button>
</template>
②監聽多個數據源
<script setup>
import { ref, watch } from 'vue';const x = ref(0);
const y = ref(0);// 監聽多個 ref
watch([x, y], ([newX, newY], [oldX, oldY]) => {console.log(`坐標變化:(${oldX},${oldY}) → (${newX},${newY})`);
});
</script>
③深度監聽對象
<script setup>
import { reactive, watch } from 'vue';const user = reactive({ name: 'Alice', address: { city: 'Beijing' }
});// 深度監聽 user 對象
watch(user,(newVal) => {console.log('用戶信息變化:', newVal);},{ deep: true }
);const changeCity = () => {user.address.city = 'Shanghai'; // 觸發回調
};
</script>
④監聽對象中某個特定屬性
通過?getter 函數?明確指定要監聽的屬性,確保能精準追蹤目標屬性的變化。
<script setup>
import { reactive, watch } from 'vue';const obj = reactive({ a: 1, b: 2 });// 監聽 obj.a 的變化
watch(() => obj.a, // getter 函數返回目標屬性(newVal, oldVal) => {console.log(`obj.a 從 ${oldVal} 變為 ${newVal}`);}
);// 修改 obj.a 會觸發回調
const changeA = () => {obj.a++;
};
</script><template><button @click="changeA">修改 obj.a</button>
</template>
注意:
- getter 函數?只是監聽對象中某個指明的屬性;比如;監聽()=>obj.a;只有當obj.a變化了才會觸發回調函數(obj.b變化了不觸發);
- 深度監聽{deep:true}:是監聽整個對象及其所有嵌套屬性;比如監聽obj對象,對象的任意屬性(包括嵌套)變化時均會觸發;
newVal
?和?oldVal
?相同(因為對象引用未變)
2、watchEffect
3.2.1.?核心特點
-
自動收集依賴:回調函數中使用的響應式數據會被自動追蹤。
-
立即執行:回調函數會立即執行一次。
-
適合場景:依賴多個數據且不需要舊值的場景。
3.2.2.?基本語法
import { watchEffect } from 'vue';const stop = watchEffect((onCleanup) => {// 自動追蹤依賴// 執行副作用邏輯return () => { // 清理邏輯(如取消請求)};
});// 手動停止監聽
stop();
3.2.3.?代碼示例
①自動跟著依賴
<script setup>
import { ref, watchEffect } from 'vue';const count = ref(0);
const double = ref(0);// 自動追蹤 count 和 double
watchEffect(() => {double.value = count.value * 2;console.log(`double 值:${double.value}`);
});const increment = () => {count.value++;
};
</script>
②清理副作用
<script setup>
import { watchEffect } from 'vue';watchEffect((onCleanup) => {const timer = setInterval(() => {console.log('定時器運行中...');}, 1000);// 清理定時器(組件卸載或依賴變化時觸發)onCleanup(() => {clearInterval(timer);console.log('定時器已清理');});
});
</script>
3.2.4.?注意事項
①避免無限循環
在回調中修改監聽的數據可能導致無限循環:
// ? 錯誤:修改 count 會再次觸發回調
watchEffect(() => {count.value++;
});
②異步操作處理
在?watchEffect
?中處理異步操作時,使用?onCleanup
?避免競態條件:
watchEffect(async (onCleanup) => {let isValid = true;onCleanup(() => (isValid = false)); // 標記請求已過期const data = await fetchData();if (isValid) {// 處理有效數據}
});
③手動停止監聽
在組件卸載時手動停止監聽(尤其是?watchEffect
):
const stop = watchEffect(() => { /* ... */ });// 組件卸載時
onUnmounted(() => stop());
④性能優化
-
使用?
flush: 'post'
?確保回調在 DOM 更新后執行:watchEffect(() => { /* ... */ }, { flush: 'post' });
-
避免在?
watch
?中深度監聽大型對象。
3、flush進一步解釋:
`flush` 是 Vue 3 中 `watch` 和 `watchEffect` 的一個配置選項,用于控制**副作用函數(回調)的執行時機**。
簡單來說,它決定了當數據變化時,監聽的回調函數是立即執行,還是等 DOM 更新后再執行。
3.3.1.flush
?的三種模式
-
'pre'
(默認值)-
觸發時機:在組件更新前執行回調。
-
特點:此時 DOM 尚未更新,因此無法訪問最新的 DOM 狀態。
-
適用場景:需要在數據變化后、DOM 更新前執行邏輯(如驗證數據或準備某些狀態)。
-
-
'post'
-
觸發時機:在組件更新后執行回調。
-
特點:此時 DOM 已更新,可以安全操作 DOM 或訪問最新的渲染結果。
-
適用場景:需要依賴 DOM 更新的操作(如測量元素尺寸、觸發第三方庫的布局計算)。
-
-
'sync'
-
觸發時機:同步觸發回調,即依賴的數據變化后立即執行。
-
特點:可能導致回調被頻繁觸發(如一個操作修改多個依賴值),需注意性能問題。
-
適用場景:極少用,僅在需要極低延遲響應時使用(如實現復雜的同步邏輯)。
-
3.3.2.flush
?的使用場景例子
①`flush: 'post'`(常用)
在 DOM 更新后執行操作(如聚焦輸入框)
watchEffect(() => {// DOM 已更新,可以安全操作inputRef.value.focus();},{ flush: 'post' } // 確保 DOM 更新后執行);
②flush: 'pre'(默認值)
在組件更新前處理數據(如讀取舊 DOM 狀態)
watch(() => data.value,(newVal, oldVal) => {// 組件更新前記錄舊 DOM 高度const oldHeight = element.clientHeight;},{ flush: 'pre' } // 默認值,可省略
);
③flush: 'sync'(特殊需求)
立即響應數據變化(如調試或實時同步數據)
watch(() => count.value,(newVal) => {console.log('Count 立即變化為:', newVal); // 同步輸出},{ flush: 'sync' }
);
4、副作用的解釋:
3.4.1:副作用的概述
-
副作用:在?
watchEffect
?回調中執行的異步或外部操作(如定時器、事件監聽、請求等)。 -
清理函數:通過?
onCleanup
?注冊的函數,用于在以下時機執行清理邏輯:-
依賴變化:當依賴的響應式數據變化,觸發新的回調前。
-
組件卸載:當組件卸載時,自動清理所有未完成的副作用。
-
//基本語法
watchEffect((onCleanup) => {// 執行副作用(如定時器、請求)const timer = setInterval(() => {console.log('定時器運行中...');}, 1000);// 注冊清理函數onCleanup(() => {clearInterval(timer); // 清理定時器console.log('定時器已清理');});
});
3.4.2:代碼執行流程
①副作用產生
-
觸發時機:
當組件掛載時,watchEffect
?會立即執行回調函數。 -
操作內容:
在回調中創建了一個定時器?timer
,它會每秒執行一次?console.log('每秒執行一次')
。
②清理副作用
-
觸發清理的時機:
-
依賴變化:如果回調中使用了響應式數據(如?
ref
?或?reactive
),當這些數據變化時,Vue 會重新執行回調函數。在重新執行前,會先調用上一次注冊的清理函數。 -
組件卸載:當組件被銷毀時,Vue 會自動觸發清理函數。
-
-
清理操作:
調用?clearInterval(timer)
?清除定時器,停止日志輸出,避免內存泄漏。