Vue3中customRef自定義ref過(guò)程
一、customRef 基礎(chǔ)概念
??customRef?? 是 Vue3 提供的高級(jí)響應(yīng)式 API,用于創(chuàng)建??自定義行為的 ref 對(duì)象??。
與普通 ref(ref())的固定 getter/setter 邏輯不同,customRef 允許開發(fā)者??完全控制依賴追蹤和更新觸發(fā)??,適用于防抖、節(jié)流、異步更新、數(shù)據(jù)驗(yàn)證等復(fù)雜場(chǎng)景。
其核心原理是通過(guò)??工廠函數(shù)??返回包含 get 和 set 方法的對(duì)象:
get():讀取數(shù)據(jù)時(shí)觸發(fā),需調(diào)用track()告訴 Vue“這個(gè)值被依賴”;set(newValue):修改數(shù)據(jù)時(shí)觸發(fā),需調(diào)用trigger()告訴 Vue“這個(gè)值更新了”。
二、customRef 基本用法
1. 創(chuàng)建簡(jiǎn)單 customRef
以下代碼實(shí)現(xiàn)一個(gè)??基礎(chǔ) customRef??,功能與普通 ref 類型,但能更清晰地看到依賴追蹤和更新觸發(fā)的流程:
import { customRef } from 'vue';
// 工廠函數(shù):接收初始值,返回 customRef 對(duì)象
function useBasicRef(initialValue) {
let value = initialValue; // 閉包保存值
return customRef((track, trigger) => {
return {
// 讀取值時(shí)觸發(fā),調(diào)用 track() 收集依賴
get() {
track(); // 告訴 Vue 這個(gè)值被依賴(如模板中的 {{ value }})
console.log('獲取值:', value);
return value;
},
// 修改值時(shí)觸發(fā),調(diào)用 trigger() 通知更新
set(newValue) {
value = newValue; // 更新值
console.log('設(shè)置值:', newValue);
trigger(); // 通知 Vue 重新渲染依賴該值的組件
}
};
});
}
// 在組件中使用
const basicRef = useBasicRef('初始值');2. 與普通 ref 的區(qū)別
| 特性 | 普通 ref (ref()) | customRef (customRef()) |
|---|---|---|
| ??邏輯控制?? | 固定 getter/setter(僅實(shí)現(xiàn)基本響應(yīng)式) | 完全自定義(可插入驗(yàn)證、防抖等邏輯) |
| ??靈活性?? | 低 | 高(滿足復(fù)雜場(chǎng)景需求) |
| ??適用場(chǎng)景?? | 基本響應(yīng)式需求(如存儲(chǔ)用戶輸入) | 需要特殊行為的場(chǎng)景(如延遲更新) |
三、常見應(yīng)用場(chǎng)景與實(shí)現(xiàn)
1. 防抖 ref(延遲更新)
??場(chǎng)景??:用戶輸入時(shí),避免頻繁觸發(fā)視圖更新(如搜索框輸入),僅在停止輸入后更新。
import { customRef } from 'vue';
function useDebouncedRef(initialValue, delay = 500) {
let timer = null; // 閉包保存定時(shí)器
let value = initialValue;
return customRef((track, trigger) => {
return {
get() {
track(); // 收集依賴
return value;
},
set(newValue) {
clearTimeout(timer); // 清除之前的定時(shí)器
timer = setTimeout(() => {
value = newValue; // 延遲后更新值
trigger(); // 觸發(fā)更新
}, delay);
}
};
});
}
// 在組件中使用
const searchQuery = useDebouncedRef('', 500);
// 模板中:<input v-model="searchQuery" />??效果??:用戶輸入時(shí),視圖不會(huì)立即更新,僅在停止輸入 500ms 后更新。
2. 數(shù)據(jù)驗(yàn)證 ref
??場(chǎng)景??:輸入數(shù)據(jù)時(shí),實(shí)時(shí)驗(yàn)證合法性(如郵箱格式、年齡范圍),若不合法則提示錯(cuò)誤。
import { customRef } from 'vue';
function useValidatedRef(initialValue, validator) {
let value = initialValue;
let error = null;
return customRef((track, trigger) => {
return {
get() {
track(); // 收集依賴
return { value, error }; // 返回值和錯(cuò)誤信息
},
set(newValue) {
const validationResult = validator(newValue); // 驗(yàn)證數(shù)據(jù)
if (validationResult === true) {
value = newValue; // 驗(yàn)證通過(guò),更新值
error = null;
} else {
error = validationResult; // 驗(yàn)證失敗,保存錯(cuò)誤信息
}
trigger(); // 觸發(fā)更新(視圖會(huì)顯示錯(cuò)誤信息)
}
};
});
}
// 驗(yàn)證函數(shù):檢查年齡是否在 18-120 之間
const ageValidator = (value) => {
if (typeof value !== 'number' || value < 18 || value > 120) {
return '年齡必須在 18-120 之間';
}
return true;
};
// 在組件中使用
const age = useValidatedRef(20, ageValidator);
// 模板中:<input v-model.number="age.value" /><span v-if="age.error" style="color: red">{{ age.error }}</span>??效果??:輸入非法年齡(如 10)時(shí),會(huì)顯示錯(cuò)誤提示。
3. 異步 ref(異步更新)
??場(chǎng)景??:從服務(wù)器獲取數(shù)據(jù)后,更新 ref 的值(如用戶信息加載)。
import { customRef } from 'vue';
function useAsyncRef(asyncFunction) {
let value = null;
let isLoading = true;
return customRef((track, trigger) => {
return {
get() {
track(); // 收集依賴
return { value, isLoading }; // 返回值和加載狀態(tài)
},
set(newValue) {
// 觸發(fā)異步操作(如獲取用戶信息)
asyncFunction(newValue).then((data) => {
value = data; // 更新值
isLoading = false; // 結(jié)束加載
trigger(); // 觸發(fā)更新
}).catch((error) => {
console.error('異步操作失敗:', error);
isLoading = false; // 結(jié)束加載
trigger(); // 觸發(fā)更新
});
}
};
});
}
// 模擬異步函數(shù):獲取用戶信息
const fetchUserInfo = (userId) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: userId, name: '張三' });
}, 1000);
});
};
// 在組件中使用
const userInfo = useAsyncRef(fetchUserInfo);
// 模板中:<div v-if="userInfo.isLoading">加載中...</div><div v-else>{{ userInfo.value.name }}</div>??效果??:設(shè)置 userInfo.value 為 userId 后,會(huì)顯示“加載中...”,1 秒后顯示用戶姓名。
四、best 實(shí)踐
1. 封裝為組合式函數(shù)
將 customRef 邏輯封裝為獨(dú)立的組合式函數(shù)(如 useDebouncedRef、useValidatedRef),提高代碼復(fù)用性。避免在組件中重復(fù)編寫 get/set 邏輯。
2. 正確處理依賴
在 get 方法中??必須調(diào)用 track()??,否則 Vue 無(wú)法追蹤依賴,導(dǎo)致視圖不更新;在 set 方法中??必須調(diào)用 trigger()??,否則視圖不會(huì)重新渲染。
3. 避免內(nèi)存泄漏
若 customRef 中使用了定時(shí)器、事件監(jiān)聽器等資源,需在組件卸載時(shí)清理(如 onUnmounted 鉤子中清除定時(shí)器),防止內(nèi)存泄漏。
4. 性能優(yōu)化
- 避免在
get方法中執(zhí)行昂貴計(jì)算(如復(fù)雜循環(huán)),可將結(jié)果緩存; - 在
set方法中,可通過(guò)比較新舊值(如if (newValue !== value))避免不必要的更新。
通過(guò)以上教程,你可以掌握 Vue3 中 customRef 的基本用法和常見場(chǎng)景,靈活實(shí)現(xiàn)自定義響應(yīng)式邏輯。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue3源碼分析組件掛載創(chuàng)建虛擬節(jié)點(diǎn)
這篇文章主要為大家介紹了Vue3源碼分析組件掛載創(chuàng)建虛擬節(jié)點(diǎn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
antd日期選擇器禁止選擇當(dāng)天之前的時(shí)間操作
這篇文章主要介紹了antd日期選擇器禁止選擇當(dāng)天之前的時(shí)間操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
vue實(shí)現(xiàn)菜單權(quán)限控制的示例代碼
這篇文章主要介紹了vue實(shí)現(xiàn)菜單權(quán)限控制的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
vxe-table表格組件,編輯單元格詳解(el-table組件同樣適用)
這篇文章主要介紹了vxe-table表格組件,編輯單元格詳解(el-table組件同樣適用),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
vue自定義穿梭框支持遠(yuǎn)程滾動(dòng)加載的實(shí)現(xiàn)方法
這篇文章主要介紹了vue自定義穿梭框支持遠(yuǎn)程滾動(dòng)加載,iview是全局注入,基本使用原先的類名進(jìn)行二次創(chuàng)建公共組件,修改基礎(chǔ)js實(shí)現(xiàn)邏輯,本文結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
vue滾動(dòng)條滾動(dòng)到頂部或者底部的方法
這篇文章主要給大家介紹了關(guān)于vue滾動(dòng)條滾動(dòng)到頂部或者底部的相關(guān)資料,文中通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
Vue3中ref和reactive的基本使用及區(qū)別詳析
這篇文章主要給大家介紹了關(guān)于Vue3中ref和reactive的基本使用及區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-07-07

