Vue3中級指南-Compostition API詳解
Compostition API集合,解決Vue2組件開發(fā)問題
更好的TypeScript支持
新的Api支持
什么是reactive?
- reactive是vue3中提供的實現(xiàn)響應(yīng)式數(shù)據(jù)的方法
-
在vue2中響應(yīng)式數(shù)據(jù)是通過defineProperty來實現(xiàn)的
- 因為有缺陷,在處理數(shù)組方面,所以vue3中響應(yīng)式數(shù)據(jù)是通過ES6的Proxy來實現(xiàn)的
reactive注意點
- reactive參數(shù)必須是對象(json/arr)
-
如果給reactive傳遞了其他對象
- 默認(rèn)情況下修改對象,界面不會自動更新。
- 如果想更新,可以通過重新賦值的方式。
什么是ref?
- ref和reactive一樣,也用用來實現(xiàn)響應(yīng)式數(shù)據(jù)的方法
- 由于reactive必須傳遞一個對象,所有導(dǎo)致在企業(yè)開發(fā)中如果我們只想讓某個變量實現(xiàn)響應(yīng)式的時候會非常麻煩,所有vue3就給我們提供了ref方法,實現(xiàn)對簡單之的監(jiān)聽
ref本質(zhì)
-
ref底層的本質(zhì)還是reactive,系統(tǒng)會自動根據(jù)我們給ref傳入的值將它轉(zhuǎn)換成以下這樣
ref(xxx)?=>?reactive({value:xxx})
ref注意點
- 在vue中使用ref聲明的變量在模版中不需要通過.value獲取,因為在template模版中,vue會自動給我們添加.value
- 在js中使用ref的值或者更改ref的值必須通過.value獲取和更改
遞歸監(jiān)聽
-
默認(rèn)情況下,無論是通過ref還是reactive都是遞歸監(jiān)聽
-
遞歸監(jiān)聽存在的問題,如果數(shù)據(jù)量較大,非常消耗性能
-
非遞歸監(jiān)聽
shallowReactive
????let?state?=?shallowReactive({
??????a:?"a",
??????b:?{
????????c:?"c",
????????d:?{
??????????e:?"e"
????????}
??????}
????})
注意:shallowReactive用于創(chuàng)建非遞歸監(jiān)聽的屬性,只會監(jiān)聽第一層
shallowRef
????let?state?=?shallowRef({
??????a:?"a",
??????b:?{
????????c:?"c",
????????d:?{
??????????e:?"e"
????????}
??????}
????})
注意:shallowRef監(jiān)聽的是.value的變化。并不是shallowReactive第一層的變化。
triggerRef
????let?state?=?shallowRef({
??????a:?"a",
??????b:?{
????????c:?"c",
????????d:?{
??????????e:?"e"
????????}
??????}
????})
????
????//?修改后頁面并不會更新視圖
????state.b.d.e?=?"ee"
????//?主動觸發(fā)視圖更新
????triggerRef(state)
-
應(yīng)用場景
一半情況下我們使用ref和reactive即可,只有在需要監(jiān)聽的數(shù)據(jù)量比較大的時候,我們才使用shallowRef/shallowReactive
toRaw
了解toRaw之前我們可以先看下以下的一個小列子來理解為啥需要用到toRaw
<script?lang="ts">
import?{?reactive?}?from?"vue";
export?default?{
??name:?"App",
??setup()?{
????let?data?=?{
??????name:?"只會番茄炒蛋",
??????age:?18,
????};
????let?state?=?reactive(data);
????
????function?changeAge()?{
??????//?state.age?+=?1;
??????data.age?+=?1;
??????console.log(data);
??????console.log(state);
????}
????return?{
??????state,
??????changeAge,
????};
??},
};
</script>
我們會發(fā)現(xiàn),數(shù)據(jù)改變了但是視圖并沒有更新。這里我們明白的一點就是data和state是引用關(guān)系。state的本質(zhì)是一個Proxy對象,在這個Proxy對象中引用了data
如果直接修改data,數(shù)據(jù)是改變的,但是無法觸發(fā)頁面的更新
只有通過包裝之后的對象來修改,才會觸發(fā)頁面的更新
這個方法有啥作用場景就是, 當(dāng)我們只想修改數(shù)據(jù),但是不想視圖發(fā)生變化時使用。因為ref和reactive數(shù)據(jù)類型的特點就是每次修改數(shù)據(jù)都會被追蹤,都會更新ui界面。非常消耗性能。如果我們有一些操作不需要追蹤和更新頁面,那么這時候就可以通過toRaw方法拿到他的原始數(shù)據(jù)。這樣就能夠節(jié)省性能
總結(jié):toRaw方法從響應(yīng)式數(shù)據(jù)獲取原始數(shù)據(jù)
注意:當(dāng)我們通過toRaw獲取ref類型的原始數(shù)據(jù)時會發(fā)現(xiàn)獲取不到。
ref本質(zhì):reactive
Ref(obj) => reactive({value: obj})
這也是為什么需要通過.value來獲取ref創(chuàng)建的數(shù)據(jù)
總結(jié):如果想要通過toRaw拿到ref類型的原始數(shù)據(jù)(創(chuàng)建時傳入的那個數(shù)據(jù))那么久必須明確告訴toRaw方法,要獲取的時.value的值。因為經(jīng)過Vue處理之后,.value中保存的才是當(dāng)初的原始數(shù)據(jù)。
<script?lang="ts">
import?{?ref,?toRaw?}?from?"vue";
export?default?{
??name:?"App",
??setup()?{
????let?data?=?{
??????name:?"只會番茄炒蛋",
??????age:?18,
????};
????let?state?=?ref(data);
????let?data2?=?toRaw(state.value);
????console.log(data2);
????function?changeAge()?{
??????//?state.age?+=?1;
??????data.age?+=?1;
??????console.log(data);
??????console.log(state);
????}
????return?{
??????state,
??????changeAge,
????};
??},
};
</script>
Markrow
永遠(yuǎn)不會被追蹤的數(shù)據(jù),被markrow聲明的變量永遠(yuǎn)不會被追蹤,即使被reactive或者ref聲明。修改數(shù)據(jù)也不會改變數(shù)據(jù),更不會觸發(fā)視圖更新
ref和toRef的區(qū)別
- ref 和toRef 修改響應(yīng)式數(shù)據(jù)都會會影響以前的數(shù)據(jù)
- ref 數(shù)據(jù)發(fā)生改變,界面就會自動更新
- toRef數(shù)據(jù)發(fā)生改變,界面也不會自動更新
toRef應(yīng)用場景:如果想讓響應(yīng)式數(shù)據(jù)和以前的數(shù)據(jù)關(guān)聯(lián)起來,并且更新響應(yīng)式數(shù)據(jù)之后還不想更新UI,那么就可以使用。
toRefs
將響應(yīng)式對象轉(zhuǎn)換為普通對象,其中結(jié)果對象的每個 property 都是指向原始對象相應(yīng) property 的 ref。
或者說將一個對象身上的所有屬性變?yōu)轫憫?yīng)式數(shù)據(jù)可以使用。以上這些方法都是為了提升性能。主要用的多的還是ref和reactive
customRef
創(chuàng)建一個自定義的 ref,并對其依賴項跟蹤和更新觸發(fā)進行顯式控制。它需要一個工廠函數(shù),該函數(shù)接收 track 和 trigger 函數(shù)作為參數(shù),并且應(yīng)該返回一個帶有 get 和 set 的對象。
為什么要自定義ref我們下面再說。先說如何實現(xiàn)
<script?lang="ts">
import?{?customRef?}?from?"vue";
function?myRef(value)?{
??return?customRef((track,?trigger)?=>?{
????return?{
??????get()?{
????????//?track()方法告訴vue這個數(shù)據(jù)是要被追蹤的
????????track();
????????console.log("get",?value);
????????return?value;
??????},
??????set(newValue)?{
????????value?=?newValue;
????????console.log("set",?value);
????????//?trigger()方法告訴vue更新視圖
????????trigger();
??????},
????};
??});
}
export?default?{
??name:?"App",
??setup()?{
????let?state?=?myRef(18);
????function?change()?{
??????state.value?=?20;
????}
????return?{
??????state,
??????change,
????};
??},
};
</script>
為什么要自定義ref
首先我們要知道一點的是setup函數(shù)只能是一個同步的函數(shù),不能是一個異步的函數(shù)。至于為什么不能是一個異步的函數(shù)大家在setup前面加上async讓后正常聲明變量在頁面中顯示就知道原因了。
所以當(dāng)我們在業(yè)務(wù)中需要發(fā)起ajax請求獲取數(shù)據(jù)的時候,且想通過async await來獲取數(shù)據(jù)就行不通了。
那么無法使用async await的時候就意味著我們的代碼中可能會出現(xiàn)大量的回調(diào)函數(shù)。
那么我們還想按照以前同步代碼的方式來書寫 就可以考慮使用自定義ref。
<script?lang="ts">
import?{?customRef?}?from?"vue";
function?myRef(value)?{
??return?customRef((track,?trigger)?=>?{
????fetch(value)
??????.then(async?(res)?=>?{
????????value?=?await?res.json();
????????console.log(value);
????????trigger();
??????})
??????.catch((reason)?=>?{
????????console.log(reason);
??????});
????return?{
??????get()?{
????????//?track()方法告訴vue這個數(shù)據(jù)是要被追蹤的
????????track();
????????console.log("get",?value);
????????return?value;
??????},
??????set(newValue)?{
????????value?=?newValue;
????????console.log("set",?value);
????????//?trigger()方法告訴vue更新視圖
????????trigger();
??????},
????};
??});
}
export?default?{
??name:?"App",
??setup()?{
????let?state?=?myRef("../public/data.json");
????function?change()?{
??????state.value?=?20;
????}
????return?{
??????state,
??????change,
????};
??},
};
</script>
ref獲取元素
vue3和vue2不同, vue3并沒有this.$屬性的那些東西了。想要獲取元素也很簡單
<template>
??<div>
????<div?ref="box">我是box</div>
??</div>
</template>
<script?lang="ts">
import?{?onMounted,?ref?}?from?"vue";
export?default?{
??name:?"App",
??setup()?{
????let?box?=?ref(null);
????onMounted(()?=>?{
??????console.log(box.value);
????});
????return?{
??????box,
????};
??},
};
</script>
這里注意為什么打印的是box.value 和上面ref聲明變量獲取的原因一致。
為什么在onMounted中打印。這點生命周期和2是一致的。因為setup和created還有beforeC reated一樣,這這期間dom還沒有創(chuàng)建
readonly家族
- readonly
接受一個對象 (響應(yīng)式或純對象) 或 ref 并返回原始對象的只讀代理。只讀代理是深層的:任何被訪問的嵌套 property 也是只讀的。
用于創(chuàng)建一個只讀屬性,并且是遞歸只讀
- shallowReadonly
用于創(chuàng)建一個只讀的數(shù)據(jù),但是不是遞歸只讀。只有第一層只讀
- isReadonly
用于判斷屬性是否是一個readonly返回布爾值
這里大家可能會有疑問那么我直接使用const聲明變量不就行了嗎。這里要注意。const通常用于聲明常量,且通常是聲明原始類型。但是如果聲明引用類型。隨便變量不可重新賦值。但是引用類型的屬性的值是可以更改的。
關(guān)于響應(yīng)式數(shù)據(jù)本質(zhì)上的理解
-
在vue2.x中是通過defineProperty來實現(xiàn)響應(yīng)式數(shù)據(jù)的。但是有部分缺陷,例如數(shù)組的處理
-
在vue3.x中是通過Proxy來實現(xiàn)響應(yīng)式數(shù)據(jù)的
//?簡單實現(xiàn)
let?obj?=?{?name:?"番茄",?age:?18?}
let?state?=?new?Proxy(obj,?{
????get(obj,?key)?{
????????console.log(obj,?key);?//?{name:?'番茄',?age:?18}?name
????},
????set(obj,?key,?value)?{
????????console.log(obj,?key,?value);?//?{name:?'番茄',?age:?18}?name?炒蛋
????????obj[key]?=?value
????????console.log("更新UI視圖");
???????return?true
????}
})
console.log(state.name);?//?番茄
state.name?=?"炒蛋"
console.log(state);?//?Proxy?{name:?'炒蛋',?age:?18}
通過上述簡單的列子。我們就可以在獲取值和設(shè)置值的做很多事情了。
注意點:set方法一定要給返回值告訴此次操作完成成功。因為在set操作中可能不止做一次修改例如數(shù)組
proxy代理數(shù)組
//?簡單實現(xiàn)
let?arr?=?[1,?2,?3]
let?state?=?new?Proxy(arr,?{
????get(obj,?key)?{
????????console.log(obj,?key);?//?[1,?2,?3]?1
????????return?obj[key]
????},
????set(obj,?key,?value)?{
????????/**
?????????*??[?1,?2,?3?]?push
????????????[?1,?2,?3?]?length
????????????[?1,?2,?3?]?3?4
????????????更新UI視圖
????????????[?1,?2,?3,?4?]?length?4
????????????更新UI視圖
????????????[?1,?2,?3,?4?]
?????????*/
????????console.log(obj,?key,?value);
????????obj[key]?=?value
????????console.log("更新UI視圖");
????????return?true
????}
})
//?console.log(state[1])?//?2
state.push(4)
console.log(state);
通過上述代碼我們會發(fā)現(xiàn)set方法中執(zhí)行了兩次。一次是[ 1, 2, 3 ] 3 4, 一次是[ 1, 2, 3, 4 ] length 4
set方法一定通過返回值告訴當(dāng)前的操作是否成功。如果將上面的return ture注視掉就會報錯
state.push(4)
??????^
TypeError:?'set'?on?proxy:?trap?returned?falsish?for?property?'3'
????at?Proxy.push?(<anonymous>)
????at?Object.<anonymous>?(/Users/cctvabu/vite-vue3-project/proxy.js:26:7)
????at?Module._compile?(internal/modules/cjs/loader.js:1063:30)
????at?Object.Module._extensions..js?(internal/modules/cjs/loader.js:1092:10)
????at?Module.load?(internal/modules/cjs/loader.js:928:32)
????at?Function.Module._load?(internal/modules/cjs/loader.js:769:14)
????at?Function.executeUserEntryPoint?[as?runMain]?(internal/modules/run_main.js:72:12)
????at?internal/main/run_main_module.js:17:47
所以注意在set方法中一定要通過返回值告訴當(dāng)前操作是否成功
以上呢就是我學(xué)習(xí)了解到的部分api知識。后續(xù)還在學(xué)習(xí)和補充中。當(dāng)然有不對的地方請評論指出,我查閱資料修改。
找工作
本人最近準(zhǔn)備跳槽找工作。有介紹工作的可以聯(lián)系我哈~
