vue3的reactive和ref差別
vue3的資料雙向綁定,大家都明白是proxy資料代理,但是在定義響應式資料的時候,有ref和reactive兩種方式,如果判斷該使用什麼方式,是大家一直不很清楚地問題。
首先,明白一點,Vue3 的 reactive 和 ref 是借助了vue3的Proxy代理來實作的。
reactive方式
參數: 對象或數組
作用:建立原始資料對象的響應式副本,即将「引用資料類型」的資料轉換為「響應式」資料,這個操作就是實作頁面的響應式
reactive的實作:
// 首先,判斷是否為對象
const isObject =( val )=> {
return val !== null && typeof val === 'object';
}
// 判斷key是否存在
const hasOwn = (value, key) => {
return Object.prototype.hasOwnProperty.call(value, key);
}
export function reactive(value) {
// 首先先判斷是否為對象
if (!isObject(value)) return target;
const handler = {
get(value, key, receiver) {
console.log(`擷取對象屬性${key}值`)
// 收集依賴 ...
const result = Reflect.get(value, key, receiver)
// 深度監聽(惰性)
if (isObject(result)) {
return reactive(result);
}
return result;
},
set(value, key, value, receiver) {
console.log(`設定對象屬性${key}值`)
// 首先先擷取舊值
const oldValue = Reflect.get(value, key, reactive)
let res = Reflect.set(value, key, value, receiver);
if (res && oldValue !== value) {
// 更新操作 ...
}
return res
},
deleteProperty(value, key) {
console.log(`删除對象屬性${key}值`)
// 先判斷是否有key
const hadKey = hasOwn(value, key)
const res = Reflect.deleteProperty(value, key)
if (hadKey && res) {
// 更新操作 ...
}
return res
},
// 其他方法
// ...
}
return new Proxy(value, handler)
}
const obj = { a: { b: { c: 1 } } };
const proxy = reactive(obj);
proxy.a.b.c = 100;
// 擷取對象屬性a值
// 擷取a值對象屬性b值
// 設定b值對象屬性c值 100
至此,引用資料類型的對象我們被我們它轉化成響應式對象了。需要注意的是,Proxy對象隻能代理引用資料類型的對象。
對于基本資料類型如何實作響應式呢?
vue源碼給出的的解決方案是把基本資料類型套一個對象外殼變成一個對象:這個對象隻有一個value屬性,value屬性的值就等于這個基本資料類型的值。然後,就可以用reactive方法将這個對象,變成響應式的Proxy對象。
實際上就是: ref(0) 等同于 reactive( { value:0 })
ref方式
參數:基本資料類型/引用類型/DOM的ref屬性值
作用:把基本類型的資料變為響應式資料。
ref 實作 Vue3 源碼
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)
}
const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
class RefImpl<T> {
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)
}
}
}
在vue3中調用ref函數時會js會先new 一個class,這個class監聽了value屬性的 get 和 set方法 ,實作了在get中收集依賴,在set中觸發依賴的雙向綁定過程,而如果需要對傳入參數深層監聽的話,就需要調用我們上面提到的reactive方法。
即:
const a = ref(0); // 通過監聽對象a的value屬性實作響應式
const b = ref({a: 6}); // 調用reactive方法對對象進行深度監聽,b.value時擷取的則是這個響應式對象
簡單實作下:
// 自定義ref
function ref(target) {
const result = { // 這裡在源碼中展現為一個類 RefImpl
_value: reactive(target), // target傳給reactive方法做響應式處理,如果是對象的話就變成響應式
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 資料已更新, 去更新界面')
}
}
return result
}
// 測試
const ref = ref(9);
ref.value = 6;
const ref = ref({a: 4});
ref.value.a = 6;
ref 方法包裝的資料,zai方法中通路需要使用.value 屬性,但在模闆展示的時候不需要,Vue解析頁面的時候會自動添加。
總結
- reactive 将引用資料類型值變為響應式,使用Proxy實作。
- ref 可将基本資料類型和引用資料類型都變成響應式,通過套殼成對象,監聽對象class的value屬性的get和set方法實作,但是當傳入的值為引用資料類型時實際上内部還是使用reactive 方法進行的處理。
- 推薦基本資料類型使用ref,引用資料類型使用 reactive。