您好,登錄后才能下訂單哦!
這篇文章主要講解了“Vue3中reactive和ref的區(qū)別有哪些”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Vue3中reactive和ref的區(qū)別有哪些”吧!
vue2的響應(yīng)式是通過Object.defineProperty 方法,劫持對象的getter
和setter
,在getter
中收集依賴,在setter
中觸發(fā)依賴,但是這種方式存在一些缺點:
由于是遍歷遞歸監(jiān)聽屬性,當(dāng)屬性過多或嵌套層級過深時會影響性能
無法監(jiān)聽對象新增的屬性和刪除屬性,只能監(jiān)聽對象本身存在的屬性,所以設(shè)計了$set
和$delete
如果監(jiān)聽數(shù)組的話,無法監(jiān)聽數(shù)組元素的增減,只能監(jiān)聽通過下標(biāo)可以訪問到的數(shù)組中已有的屬性,由于使用Object.defineProperty
遍歷監(jiān)聽數(shù)組原有元素過于消耗性能,vue放棄使用Object.defineProperty
監(jiān)聽數(shù)組,而采用了重寫數(shù)組原型方法的方式來監(jiān)聽對數(shù)組數(shù)據(jù)的操作,并用$set
和splice
方法來更新數(shù)組,$set
和splice
會調(diào)用重寫后的數(shù)組方法。
針對Object.defineProperty的弊病, 在 ES6 中引入了一個新的對象——Proxy(對象代理)
Proxy 對象:
用于創(chuàng)建一個對象的代理,主要用于改變對象的某些默認(rèn)行為,Proxy 可以理解成,在目標(biāo)對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫?;菊Z法如下:
/*
* target: 目標(biāo)對象
* handler: 配置對象,用來定義攔截的行為
* proxy: Proxy構(gòu)造器的實例
*/
var proxy = new Proxy(target,handler)
攔截get,取值操作
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
可以攔截的操作有:
函數(shù) | 操作 |
---|---|
get | 讀取一個值 |
set | 寫入一個值 |
has | in操作符 |
deleteProperty | Object.getPrototypeOf() |
getPrototypeOf | Object.getPrototypeOf() |
setPrototypeOf | Object.setPrototypeOf() |
isExtensible | Object.isExtensible() |
preventExtensions | Object.preventExtensions() |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor() |
defineProperty | Object.defineProperty |
ownKeys | Object.keys() Object.getOwnPropertyNames()和Object.getOwnPropertySymbols() |
apply | 調(diào)用一個函數(shù) |
construct | new一個函數(shù) |
那么使用Proxy
可以解決Vue2
中的哪些問題,總結(jié)一下:
Proxy是對整個對象的代理,而Object.defineProperty只能代理某個屬性。
對象上新增屬性,Proxy可以監(jiān)聽到,Object.defineProperty不能。
數(shù)組新增修改,Proxy可以監(jiān)聽到,Object.defineProperty不能。
若對象內(nèi)部屬性要全部遞歸代理,Proxy可以只在調(diào)用的時候遞歸,而Object.definePropery需要一次完成所有遞歸,Proxy相對更靈活,提高性能。
var target = {
a:1,
b:{
c:2,
d:{e:3}
}
}
var handler = {
get:function(target, prop, receiver){
console.log('觸發(fā)get:',prop)
return Reflect.get(target,prop)
},
set:function(target,key,value,receiver){
console.log('觸發(fā)set:',key,value)
return Reflect.set(target,key,value,receiver)
}
}
var proxy = new Proxy(target,handler)
proxy.b.d.e = 4
// 輸出 觸發(fā)get:b , 由此可見Proxy僅代理了對象外層屬性。
以上寫法只代理了對象的外層屬性,所以要想深層代理整個對象的所有屬性,需要進行遞歸處理:
var target = {
a:1,
b:{
c:2,
d:{e:3}
},
f: {z: 3}
}
var handler = {
get:function(target, prop, receiver){
var val = Reflect.get(target,prop)
console.log('觸發(fā)get:',prop)
if(val !== null && typeof val==='object') {
return new Proxy(val,handler) // 代理內(nèi)層屬性
}
return Reflect.get(target,prop)
},
set:function(target,key,value,receiver){
console.log('觸發(fā)set:',key,value)
return Reflect.set(target,key,value,receiver)
}
}
var proxy = new Proxy(target,handler)
proxy.b.d.e = 4
// 輸出 觸發(fā)get b,get d, get e
從遞歸代理可以看出,如果要代理對象的內(nèi)部屬性,Proxy可以只在屬性被調(diào)用時去設(shè)置代理(惰性),訪問了e
,就僅遞歸代理b
下面的屬性,不會額外代理其他沒有用到的深層屬性,如z
。
Reflect
的作用和意義規(guī)范語言內(nèi)部方法的所屬對象,不全都堆放在Object對象或Function等對象的原型上。如
Function.prototype.apply
Object.defineProperty
修改某些Object
方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)
在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)
則會返回false
。
讓Object
操作都變成函數(shù)行為。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
讓它們變成了函數(shù)行為。
Reflect
對象的方法與Proxy
對象的方法一一對應(yīng),只要是Proxy
對象的方法,就能在Reflect
對象上找到對應(yīng)的方法。這就讓Proxy
對象可以方便地調(diào)用對應(yīng)的Reflect
方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說,不管Proxy
怎么修改默認(rèn)行為,你總可以在Reflect
上獲取默認(rèn)行為。
reative
和ref
Vue3 的 reactive 和 ref 正是借助了Proxy來實現(xiàn)。
作用:創(chuàng)建原始對象的響應(yīng)式副本,即將「引用類型」數(shù)據(jù)轉(zhuǎn)換為「響應(yīng)式」數(shù)據(jù)
參數(shù): reactive參數(shù)必須是對象或數(shù)組
reative函數(shù)實現(xiàn):
// 判斷是否為對象
const isObject = val => val !== null && typeof val === 'object';
// 判斷key是否存在
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
export function reactive(target) {
// 首先先判斷是否為對象
if (!isObject(target)) return target;
const handler = {
get(target, key, receiver) {
console.log(`獲取對象屬性${key}值`)
// 收集依賴 ...
const result = Reflect.get(target, key, receiver)
// 深度監(jiān)聽(惰性)
if (isObject(result)) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
console.log(`設(shè)置對象屬性${key}值`)
// 首先先獲取舊值
const oldValue = Reflect.get(target, key, reactive)
let result = Reflect.set(target, key, value, receiver);
if (result && oldValue !== value) {
// 更新操作 ...
}
return result
},
deleteProperty(target, key) {
console.log(`刪除對象屬性${key}值`)
// 先判斷是否有key
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 更新操作 ...
}
return result
},
// 其他方法
// ...
}
return new Proxy(target, handler)
}
const obj = { a: { b: { c: 6 } } };
const proxy = reactive(obj);
proxy.a.b.c = 77;
// 獲取對象屬性a值
// 獲取對象屬性b值
// 設(shè)置對象屬性c值 77
至此,引用類型的對象我們已經(jīng)可以把它轉(zhuǎn)化成響應(yīng)式對象了,Proxy對象只能代理引用類型的對象,對于基本數(shù)據(jù)類型如何實現(xiàn)響應(yīng)式呢?
vue的解決方法是把基本數(shù)據(jù)類型變成一個對象:這個對象只有一個value屬性,value屬性的值就等于這個基本數(shù)據(jù)類型的值。然后,就可以用reative
方法將這個對象,變成響應(yīng)式的Proxy對象。
實際上就是: ref(0) --> reactive( { value:0 })
作用:把基本類型的數(shù)據(jù)變?yōu)轫憫?yīng)式數(shù)據(jù)。
參數(shù):
1.基本數(shù)據(jù)類型
2.引用類型
3.DOM的ref屬性值
export function ref(value?: unknown) {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl{
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
大體思路就是,調(diào)用ref函數(shù)時會new 一個類,這個類監(jiān)聽了value屬性的 get 和 set ,實現(xiàn)了在get中收集依賴,在set中觸發(fā)依賴,而如果需要對傳入?yún)?shù)深層監(jiān)聽的話,就會調(diào)用我們上面提到的reactive
方法。
即:
ref(0); // 通過監(jiān)聽對象(類)的value屬性實現(xiàn)響應(yīng)式
ref({a: 6}); // 調(diào)用reactive方法對對象進行深度監(jiān)聽
根據(jù)上面的思路我們可以自己來簡單實現(xiàn)下:
// 自定義ref
function ref(target) {
const result = { // 這里在源碼中體現(xiàn)為一個類 RefImpl
_value: reactive(target), // target傳給reactive方法做響應(yīng)式處理,如果是對象的話就變成響應(yīng)式
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 數(shù)據(jù)已更新, 去更新界面')
}
}
return result
}
// 測試
const ref = ref(9);
ref.value = 6;
const ref = ref({a: 4});
ref.value.a = 6;
ref 方法包裝的數(shù)據(jù),需要使用.value
來訪問,但在模板中不需要,Vue解析時會自動添加。
感謝各位的閱讀,以上就是“Vue3中reactive和ref的區(qū)別有哪些”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Vue3中reactive和ref的區(qū)別有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。