customRef
返回一個ref對象,可以顯式地控制依賴追蹤和觸發響應
示例
<template> <div> <p>{{obj}}</p> <button @click="inc">button</button> </div> </template> <script> import { customRef } from 'vue'; // customRef用於 自定義ref // 自定義 ref 需要提供參數傳值 function myRef(value) { // 自定義 ref 需要提供 customerRef 返回值 // customer 需要提供一個函數作為參數 // 該函數默認帶參數 track 和 trigger ,都是方法。 return customRef((track, trigger) => { return { // customer 需要提供一個對象 作為返回值 // 該對象需要包含 get 和 set 方法。 get() { // track 方法放在 get 中,用於提示這個數據是需要追蹤變化的 track(); console.log('get', value); return value; }, // set 傳入一個值作為新值,通常用於取代 value set(newValue) { console.log('set', newValue); value = newValue; // 記得觸發事件 trigger,告訴vue觸發頁面更新 trigger(); } } }) } export default { name: 'App', setup() {
// let obj = ref(18); // reactive({value: 18})
// 應用上面的自定義 ref ,使用方案和之前的 ref 是類似的。 const obj = myRef(123); function inc() { obj.value += 1; } return { obj, inc }; } } </script>
這並不是一個多么復雜的方法,如果要使用,記得是在自定義的 ref
中返回一個 customRef
,而 customRef
也要返回一個對象,相當於二重嵌套的返回。
假如我們去掉了 track
和 trigger
,那么將失去視圖層追蹤變化的能力(可以顯示控制)。如果需要進行視圖層追蹤,請注意在 set
中 value
發生變化后即刻執行 trigger
。
#實例2
考慮一個通常情況下會出現的場景,我們需要發送請求,獲取數據,而這個過程是異步的。
首先是數據,它放在 public 文件夾里。
// data.json [ { "name": "GuanYu", "id": 1 }, { "name": "ZhangFei", "id": 2 }, { "name": "MaChao", "id": 3 }, { "name": "ZhaoYun", "id": 4 }, { "name": "HuangZhong", "id": 5 } ]
如果我們並未使用 customRef
來執行自定義。fetch請求詳情:阮一峰
<template> <ul> <li v-for="item in obj" :key="item.id"> {{item.id}} - {{item.name}} </li> </ul> </template> <script> import { ref } from 'vue'; export default { name: 'App', setup() { // 創建一個空數組 ref const obj = ref([]); // 使用 fetch 異步獲取文件內容 fetch('../public/data.json') .then((res) => { return res.json(); }).then((data) => { console.log(data); obj.value = data; }).catch((err) => { console.log(err); }) return { obj, }; } } </script>
此時在setup函數中(setup函數: 只能是一個同步的函數, 不能是一個異步的函數,如async setup),有多個異步回調函數,不美觀,是否可用同步效果呢,可以采用自定義ref實現
這是一個辦法,但還有更加具有可復用性的方案。
<template> <ul> <li v-for="item in obj" :key="item.id"> {{item.id}} - {{item.name}} </li> <button @click="getNewObj">newObj</button> </ul> </template> <script> import { customRef } from 'vue'; function fetchRef(value) { return customRef((track, trigger) => { // 用於存儲獲得的數據 let ans; function getAns() { fetch(value) .then((res) => { return res.json(); }).then((data) => { console.log(data); // 將獲得的數據存儲起來 ans = data; // 提示觸發視圖層變化 trigger(); }).catch((err) => { console.log(err); }); } getAns(); return { get() { track(); //告訴vue這個數據需要追蹤變化 return ans; }, set(newValue) { value = newValue; // 修改 value 的同時再次進行數據的抓取 getAns(); } } }) } export default { name: 'App', setup() { const obj = fetchRef('../public/data.json'); // 修改數據源 function getNewObj() { obj.value = '../public/data1.json'; } return { obj, getNewObj }; } } </script>
// 注意點:
// 不能在get方法中發送網絡請求,會循環發送請求
// 渲染界面 -> 調用get -> 發送網絡請求
// 保存數據 -> 更新界面 -> 調用get
在這個方案中,我將 獲取數據的方案 封裝存儲在 自定義ref
中,在初始化的時候,調用get函數,發送請求,會在獲取數據之后,在更新頁面之前,執行 trigger
進行視圖層的變化。
而在設置新值的時候,再次觸發獲取數據的方案,從而實現復雜的雙向數據綁定。在setup函數就實現一個同步代碼方式,美觀
點擊 button
,可以改變數據,並實現視圖層的變化。
// data1.json [ { "name": "LiuBei", "id": 6 }, { "name": "CaoCao", "id": 7 }, { "name": "SunQuan", "id": 8 } ]