vue3中reactive和ref的實(shí)現(xiàn)與區(qū)別詳解
前言
reactive和ref都是vue3實(shí)現(xiàn)響應(yīng)式系統(tǒng)的api,他們是如何實(shí)現(xiàn)響應(yīng)式的呢?reactive和ref又有什么區(qū)別呢,看完這篇文章,相信答案就呼之欲出了。
effect
vue3中的reactive實(shí)現(xiàn)了數(shù)據(jù)的響應(yīng)式,說(shuō)到數(shù)據(jù)響應(yīng)式離不開(kāi)effect,effect是關(guān)鍵,effect()方法是暴露給創(chuàng)作者的一種方法,參數(shù)1是一個(gè)回調(diào)函數(shù),寫(xiě)在回調(diào)函數(shù)的代碼可以用來(lái)模擬setup執(zhí)行函數(shù)的響應(yīng)式數(shù)據(jù)被寫(xiě)在template模板的數(shù)據(jù),當(dāng)數(shù)據(jù)更新之后,將effect中的回調(diào)函數(shù)再次調(diào)用,達(dá)到數(shù)據(jù)更新便更新視圖的目的。也可以把effect叫做數(shù)據(jù)相關(guān)依賴,即記錄用到響應(yīng)式數(shù)據(jù)的代碼。參數(shù)二是一個(gè)對(duì)象,里面有l(wèi)azy屬性用來(lái)規(guī)定effect的回調(diào)函數(shù)第一次是否執(zhí)行。
<body>
<div id="app">hello</div>
<script>
let data={
name:'草原一匹狼'
}
effect(()=>{
let app = document.getElementById('app')
app.innerHTML=data.name
},{lazy:true})
setTimeout(()=>{
data.name='切'
},1000)
</script>
</body>
實(shí)現(xiàn)effect方法,保存回調(diào)函數(shù)當(dāng)數(shù)據(jù)更新再次調(diào)用回調(diào)函數(shù)達(dá)到更新視圖
export function effect(fn, options: any = {}) {//更新視圖也就是吧視圖用到數(shù)據(jù)在執(zhí)行一遍
const effect = createReactEffect(fn, options);
if (!options.lazy) {//如果lazy:false執(zhí)行
effect();
}
return effect;
}
let uid = 0;
//每次調(diào)用都會(huì)創(chuàng)建一個(gè)effect
let activeeffect;//設(shè)置當(dāng)前effect為全局變量好進(jìn)行收集
let effectStack = [];//保存effect 因?yàn)榭赡軙?huì)有多個(gè)effect
function createReactEffect(fn, options) {
const effect = function reactiveEffect() {
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect);
activeeffect = effect;
fn();
} finally {
effectStack.pop();
activeeffect = effectStack[effectStack.length - 1];
}
}
};
effect.id = uid++; //區(qū)別effect
effect._isEffect = true; //區(qū)別effect 是不是響應(yīng)式的effect
effect.raw = fn; //保存回調(diào)函數(shù)到自身
effect.options = options; //保存用戶屬性lazy
return effect;
}
reactive
了解完effect,接下來(lái)是實(shí)現(xiàn)reactive的具體過(guò)程:
- 響應(yīng)式數(shù)據(jù)用proxy代理時(shí)get收集依賴
- 響應(yīng)式數(shù)據(jù)用proxy代理時(shí)set執(zhí)行依賴
1.響應(yīng)式數(shù)據(jù)用proxy代理時(shí)get收集依賴
function createGetter(isReadonly = false, shall = false) {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver); //taget[key]
if (!isReadonly) {
//判斷是不是只讀 收集依賴
Track(target, TackOpType.GET, key);//Track收集依賴 一個(gè)響應(yīng)式屬性key對(duì)應(yīng)一個(gè)effect保存起來(lái)
}
if (shall) {
//只代理一層
return res;
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
//收集effect 獲取數(shù)據(jù)觸發(fā)get 收集依賴數(shù)據(jù)變化更新視圖 key和effect11對(duì)應(yīng)
let targetMap = new WeakMap(); //棧結(jié)構(gòu)(target ,new Map(key(key effect一一對(duì)應(yīng)),new Set(activeeffect)))
export function Track(target, type, key) {
//對(duì)應(yīng)的key
if (activeeffect == undefined) {
return;
}
let depMAp = targetMap.get(target);
if (!depMAp) {
targetMap.set(target, (depMAp = new Map()));
}
let dep = depMAp.get(key);
if (!dep) {
depMAp.set(key, (dep = new Set()));
}
if (!dep.has(activeeffect)) {
dep.add(activeeffect);
}
}
2.響應(yīng)式數(shù)據(jù)用proxy代理時(shí)set執(zhí)行依賴
function createSetter(shall = false) {
return function set(target, key, value, receive) {
const oldValue = target[key];
let hasKey =
Array.isArray(oldValue) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(target, key, value, receive); //獲取最新的值 對(duì)象數(shù)據(jù)已經(jīng)更新
if (!hasKey) {
// 新增
trigger(target, TriggerOpTypes.ADD, key, value);
} else {
// 修改數(shù)組
if (hasChange(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue);
}
}
return result;
};
}
//觸發(fā)更新依賴effect
export function trigger(target, type, key?, newValue?, oldValue?) {
const depsMap = targetMap.get(target);
let effectSet = new Set();
if (!depsMap) return;
const add = (effectAdd) => {
if (effectAdd) {
effectAdd.forEach((effect) => effectSet.add(effect));
}
};
add(depsMap.get(key)); //獲取當(dāng)前屬性的effect
//處理數(shù)組
if (key == "length" && Array.isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === "length" || key >= newValue) {
add(dep);
}
});
}else{
//處理對(duì)象
if(key !== undefined){
add(depsMap.get(key))
}
switch(type){
case TriggerOpTypes.ADD:
if(Array.isArray(target)&& isIntegerKey(key)){
add(depsMap.get('target'))
}
}
}
effectSet.forEach((effect: any) => {
if(effect.options.sch){
effect.options.sch(effect)
}else{
effect()
}
});
- 被reactive包裹的數(shù)據(jù)實(shí)現(xiàn)響應(yīng)式,首先得effec保存回調(diào)函數(shù),方便數(shù)據(jù)更新再次調(diào)用來(lái)更新視圖,
- 當(dāng)用proxy對(duì)對(duì)象代理時(shí)get方法中將視圖用到的代碼片段即相關(guān)effect與key一一對(duì)應(yīng)保存起來(lái)
- 當(dāng)數(shù)據(jù)改變用到proxy的set方法時(shí),用key找到對(duì)應(yīng)effect執(zhí)行
ref
相信大家都知道用reactive實(shí)現(xiàn)復(fù)雜數(shù)據(jù)代理用ref實(shí)現(xiàn)簡(jiǎn)單數(shù)據(jù)的代理,因?yàn)関ue用es6的proxy代理整個(gè)對(duì)象,而ref是給簡(jiǎn)單數(shù)據(jù)用一個(gè)對(duì)象包裹起來(lái)用object.defineproperty方式代理并且用.value訪問(wèn),如果用ref對(duì)一個(gè)對(duì)象進(jìn)行響應(yīng)式處理則會(huì)調(diào)用crtoReactive方法再次對(duì).value的屬性進(jìn)行代理。 根據(jù)上面已經(jīng)實(shí)現(xiàn)的reactive api實(shí)現(xiàn)ref就簡(jiǎn)單多了。
具體步驟為:
1.如果是簡(jiǎn)單數(shù)據(jù)就用object.defineproperty代理
2.如果是復(fù)雜數(shù)據(jù)就用createReactive方法再次代理
export function ref(target){
return creatRef(target)
}
//創(chuàng)建實(shí)例對(duì)象
class RefImpl{
public _v_isRef=true
public _value
public _shallow
public _rawValue
constructor(public target,public shallow){
this._shallow=shallow
this._rawValue = shallow ? target : toRaw(target)
this._value = shallow ? target : toReactive(target)
}
get value(){
Track(this,TackOpType.GET,"value")
return this.value
}
set value(newVAlue){
if(newVAlue!==this._value)
this._rawValue=newVAlue
this._value = isObject(newVAlue) ? newVAlue : toReactive(newVAlue)//這是ref不失去響應(yīng)式的關(guān)鍵如果新的值就重新代理
trigger(this,TriggerOpTypes.SET,"value",newVAlue)
}
}
function creatRef(target,shallow=false){
//創(chuàng)建ref實(shí)例對(duì)象
return new RefImpl(target,shallow)
}
到此這篇關(guān)于vue3中reactive和ref的實(shí)現(xiàn)與區(qū)別詳解的文章就介紹到這了,更多相關(guān) vue3 reactive ref內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue3.0中Ref與Reactive的區(qū)別示例詳析
- vue3?中ref和reactive的區(qū)別講解
- 前端vue3中的ref與reactive用法及區(qū)別總結(jié)
- Vue3 的ref和reactive的用法和區(qū)別示例解析
- Vue3中ref和reactive的基本使用及區(qū)別詳析
- Vue3中ref和reactive的區(qū)別及說(shuō)明
- vue3.0中ref與reactive的區(qū)別及使用場(chǎng)景分析
- Vue3中關(guān)于ref和reactive的區(qū)別分析
- vue3 ref 和reactive的區(qū)別詳解
- vue3中ref和reactive的區(qū)別舉例詳解
相關(guān)文章
Vue 前端實(shí)現(xiàn)登陸攔截及axios 攔截器的使用
這篇文章主要介紹了Vue 前端實(shí)現(xiàn)登陸攔截及axios 攔截器的使用,通過(guò)這個(gè)項(xiàng)目學(xué)習(xí)如何實(shí)現(xiàn)一個(gè)前端項(xiàng)目中所需要的 登錄及攔截、登出、token失效的攔截及對(duì)應(yīng) axios 攔截器的使用。需要的朋友可以參考下2019-07-07
深入理解Vue.js3中Reactive的實(shí)現(xiàn)
reactive是Vue 3的Composition API中的一個(gè)函數(shù),它允許你創(chuàng)建一個(gè)響應(yīng)式的數(shù)據(jù)對(duì)象,本文主要介紹了深入理解Vue.js3中Reactive的實(shí)現(xiàn),感興趣的可以了解一下2024-01-01
vue+springboot實(shí)現(xiàn)登錄功能
這篇文章主要為大家詳細(xì)介紹了vue+springboot實(shí)現(xiàn)登錄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
VUE微信H5生成二維碼海報(bào)保存在本地相冊(cè)的實(shí)現(xiàn)
本文主要介紹了VUE微信H5生成二維碼海報(bào)保存在本地相冊(cè)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
axios請(qǐng)求頭設(shè)置常見(jiàn)Content-Type和對(duì)應(yīng)參數(shù)的處理方式
這篇文章主要介紹了axios請(qǐng)求頭設(shè)置常見(jiàn)Content-Type和對(duì)應(yīng)參數(shù)的處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
vue如何解決this.refs拿取v-for下元素undefine問(wèn)題
這篇文章主要介紹了vue如何解決this.refs拿取v-for下元素undefine問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05

