淺層響應(yīng)式代理:shallowReactive
有的時候,我們并不需要嵌套屬性也具有響應(yīng)性卒废,這時可以使用shallowReactive 來獲得淺層的響應(yīng)式代理寓盗,這種方式只攔截自己的屬性的操作,不涉及嵌套的對象屬性的操作花嘶。
const personShallowReactive = shallowReactive({
name: 'jykShallowReactive',
age: 18,
contacts: {
QQ: 11111,
phone: 123456789
}
})
// 查看 shallowReactive 實例結(jié)構(gòu)
console.log('shallowReactive', objectShallowReactive)
// 獲取嵌套對象屬性
const contacts = objectShallowReactive.contacts
// 因為淺層代理琳彩,所以沒有響應(yīng)性
console.log('contacts屬性:', contacts)
// 獲取簡單類型的屬性
let name = objectShallowReactive.name
// 因為淺層代理且簡單類型誊酌,所以失去響應(yīng)性
console.log('name屬性:', name)
shallowReactive 也是用 Proxy 實現(xiàn)響應(yīng)性的,而單獨使用contacts屬性并沒有響應(yīng)性露乏,因為 shallowReactive 是淺層代理碧浊,所以不會讓嵌套對象獲得響應(yīng)性。
注意:objectShallowReactive.contacts.QQ = 123 施无,這樣修改屬性也是沒有響應(yīng)性的辉词。
單獨使用的屬性的形式:
嵌套對象和name屬性,都沒有變成響應(yīng)式猾骡。
做一個不允許響應(yīng)的標記:markRaw
有的時候我們不希望js對象變成響應(yīng)式的瑞躺,這時我們可以用markRaw 做一個標記,這樣即使使用 reactive 也不會變成響應(yīng)式兴想。
如果確定某些數(shù)據(jù)是不會變化的幢哨,那么也就不用變成響應(yīng)式,這樣可以節(jié)省一些不必要的性能開銷嫂便。
// 標記js對象
const object = markRaw({
name: 'jyk',
age: 18,
contacts: {
QQ: 11111,
phone: 123456789
}
})
// 試圖對標記的對象做相應(yīng)性代理
const retObject2 = reactive(object)
// 使用對象的屬性做相應(yīng)性代理
const retObject1 = reactive({
name: object.name
})
console.log('作為初始值:', retObject1) // 無法變成響應(yīng)性代理
console.log('無法變成響應(yīng)式:', retObject2) // 可以變成響應(yīng)性代理
運行結(jié)果:
做標記后的js對象作為參數(shù)捞镰,不會變成響應(yīng)式,但是使用屬性值作為參數(shù)毙替,還是可以變成響應(yīng)式岸售。
那么哪些地方可以用到呢?我們可以在給組件設(shè)置(引用類型的)屬性的時候使用厂画,默認情況下組件的屬性都是自帶響應(yīng)性的凸丸,但是如果父組件里設(shè)置給子組件的屬性值永遠不會發(fā)生變化,那么還變成響應(yīng)式的話袱院,就有點浪費性能的嫌疑了屎慢。
如果想節(jié)約一下的話瞭稼,可以在父組件設(shè)置屬性的時候加上markRaw標記。
深層只讀響應(yīng)式代理:readonly
有的時候雖然我們想得到一個響應(yīng)式的代理腻惠,但是只想被讀取环肘,而不希望被修改(比如組件的props,組件內(nèi)部不希望被修改)集灌,那么這時候我們可以用readonly悔雹。
readonly可以返回object、reactive或者ref的深層只讀代理绝页,我們來分別測試一下:
// object的只讀響應(yīng)代理
const objectReadonly = readonly(person)
// reactive 的只讀響應(yīng)代理
const reactiveReadonly = readonly(objectReactive)
// 查看 readonly 實例結(jié)構(gòu)
console.log('object 的readonly', objectReadonly)
console.log('reactive 的readonly', reactiveReadonly)
// 獲取嵌套對象屬性
const contacts = reactiveReadonly.contacts
console.log('contacts屬性:', contacts) // 因為深層響應(yīng)荠商,所以依然有響應(yīng)性
// 獲取簡單類型的屬性
let name = reactiveReadonly.name
console.log('name屬性:', name) // 屬性是簡單類型的寂恬,所以失去響應(yīng)性
運行結(jié)果:
- Handler续誉,明顯攔截的函數(shù)變少了,set的參數(shù)也變少了初肉,點進去看源碼酷鸦,也僅僅只有一行返回警告的代碼,這樣實現(xiàn)攔截設(shè)置屬性的操作牙咏。
- Target臼隔,指向object。
運行結(jié)果:
- Handler妄壶,這部分是一樣的摔握。
- Target,指向的不是object丁寄,而是一個Proxy代理氨淌,也就是reactive。
淺層只讀響應(yīng)代理:shallowReadonly
和readonly相對應(yīng)伊磺,shallowReadonly是淺層的只讀響應(yīng)代理盛正,和readonly的使用方式一樣,只是不會限制嵌套對象只讀屑埋。
// object 的淺層只讀代理
const objectShallowReadonly = shallowReadonly(person)
// reactive 的淺層只讀代理
const reactiveShallowReadonly = shallowReadonly(objectReactive)
shallowReadonly的結(jié)構(gòu)和 readonly 的一致豪筝,就不貼截圖了。
獲取原型:toRaw
toRaw 可以獲取 Vue 建立的代理的原型對象摘能,但是不能獲取我們自己定義的Proxy的實例的原型续崖。
toRaw大多是在Vue內(nèi)部使用,目前只發(fā)現(xiàn)在向indexedDB里面寫入數(shù)據(jù)的時候团搞,需要先用 toRaw 取原型严望,否則會報錯。
// 獲取reactive莺丑、shallowReactive著蟹、readonly墩蔓、shallowReadonly的原型
console.log('深層響應(yīng)的原型', toRaw(objectReactive))
console.log('淺層響應(yīng)的原型', toRaw(objectShallowReactive))
console.log('深層只讀的原型', toRaw(objectReadonly))
console.log('淺層只讀的原型', toRaw(objectShallowReadonly))
運行結(jié)果都是普通的object,就不貼截圖了萧豆。
類型判斷
Vue提供了三個用于判斷類型的函數(shù):
isProxy:判斷對象是否是Vue建立的Proxy代理奸披,包含reactive、readonly涮雷、shallowReactive和shallowReadonly創(chuàng)建的代理阵面,但是不會判斷自己寫的Proxy代理。
isReactive:判斷是否是reactive創(chuàng)建的代理洪鸭。如果readonly的原型是reactive样刷,那么也會返回true。
isReadonly:判斷是否是readonly览爵、shallowReadonly創(chuàng)建的代理置鼻。這個最簡單,只看代理不看target蜓竹。
我們用這三個函數(shù)判斷一下我們上面定義的這些Proxy代理箕母,看看結(jié)果如何。
我們寫點代碼對比一下:
const myProxyObject = myProxy({title:'222', __v_isReactive: false})
console.log('myProxyObject', myProxyObject)
const myProxyReactive = myProxy(objectReactive)
console.log('myProxyReactive', myProxyReactive)
// 試一試 __v_isReadonly
console.log('objectReactive', objectReactive)
console.log('__v_isReadonly'
, objectReactive.__v_isReadonly
, objectReactive.__v_isReactive
)
return {
obj: { // js對象
check1: isProxy(person),
check2: isReactive(person),
check3: isReadonly(person)
},
myproxy: { // 自己定義的Proxy object
check1: isProxy(myProxyObject),
check2: isReactive(myProxyObject),
check3: isReadonly(myProxyObject)
},
myproxyReactive: { // 自己定義的Proxy reactive
check1: isProxy(myProxyReactive),
check2: isReactive(myProxyReactive),
check3: isReadonly(myProxyReactive)
},
// 深層響應(yīng) reactive(object)
reto: { // reactive(object)
check1: isProxy(objectReactive),
check2: isReactive(objectReactive),
check3: isReadonly(objectReactive)
},
// 淺層響應(yīng) 參數(shù):object
shallowRetObj: {
check1: isProxy(objectShallowReactive),
check2: isReactive(objectShallowReactive),
check3: isReadonly(objectShallowReactive)
},
// 淺層響應(yīng) 參數(shù):reactive
shallowRetRet: {
check1: isProxy(objectShallowReactive),
check2: isReactive(objectShallowReactive),
check3: isReadonly(objectShallowReactive)
},
// 深層只讀俱济,參數(shù) object =======================
readObj: { // readonly object
check1: isProxy(objectReadonly),
check2: isReactive(objectReadonly),
check3: isReadonly(objectReadonly)
},
// 深層只讀嘶是,參數(shù) reactive
readRet: { // readonly reactive
check1: isProxy(reactiveReadonly),
check2: isReactive(reactiveReadonly),
check3: isReadonly(reactiveReadonly)
},
// 淺層只讀 參數(shù):object
shallowReadObj: {
check1: isProxy(objectShallowReadonly),
check2: isReactive(objectShallowReadonly),
check3: isReadonly(objectShallowReadonly)
},
// 淺層只讀 參數(shù):reactive
shallowReadRet: {
check1: isProxy(reactiveShallowReadonly),
check2: isReactive(reactiveShallowReadonly),
check3: isReadonly(reactiveShallowReadonly)
},
person
}
對比結(jié)果:
總結(jié)一下:
isReadonly 最簡單,只有readonly蛛碌、shallowReadonly建立的代理才會返回 true聂喇,其他的都是 false。
isProxy也比較簡單蔚携,Vue建立的代理才會返回true希太,如果是自己定義的Proxy,要看原型是誰浮梢,如果原型是 reactive(包括其他三個)的話跛十,也會返回true。
isReactive就有點復(fù)雜秕硝,reactive 建立的代理會返回 true芥映,其他的代理(包含自己寫的)還要看一下原型,如果是 reactive 的話远豺,也會返回true奈偏。
判斷依據(jù)
那么這三個函數(shù)是依據(jù)什么判斷的呢?自己做的 Proxy 無意中監(jiān)控到了“__v_isReactive”躯护,難道是隱藏屬性惊来?測試了一下,果然是這樣棺滞。
myProxy({title:'測試隱藏屬性', __v_isReactive: true})裁蚁,這樣定義一個實例矢渊,也會返回true。
reactive直接賦值的方法
使用的時候我們會發(fā)現(xiàn)一個問題枉证,如果直接給 reactive 的實例賦值的話矮男,就會“失去”響應(yīng)性,這個并不是因為 reactive 失效了室谚,而是因為 setup 只會運行一次毡鉴,return也只有一次給模板提供數(shù)據(jù)(地址)的機會,模板只能得到一開始提供的 reactive 的地址秒赤,如果后續(xù)直接對 reactive 的實例賦值操作猪瞬,會覆蓋原有的地址,產(chǎn)生一個新的Proxy代理地址入篮,然而模板并不會得到這個新地址陈瘦,還在使用“舊”地址,因為無法獲知新地址的存在崎弃,所以模板不會有變化甘晤。
那么就不能直接賦值了嗎?其實還是有方法的饲做,只需要保證地址不會發(fā)生變化即可。
對象的整體賦值的方法遏弱。
有請 ES6 的 Object.assign 登場盆均,這個方法是用來合并兩個或者多個對象的屬性的,如果屬性名稱相同后面的屬性會覆蓋前面的屬性漱逸。所以大家在使用的時候要謹慎使用泪姨,確保兩個對象的屬性就兼容的,不會沖突饰抒。
代碼如下:
Object.assign(objectReactive, {name: '合并', age: 20, newProp: '新屬性'})
數(shù)組的整體賦值的方法肮砾。
數(shù)組就方便多了,可以先清空再 push 的方式袋坑,代碼如下:
// retArray.length = 0 // 這里清空的話仗处,容易照成閃爍,所以不要急
setTimeout(() => {
const newArray = [
{ name: '11', age: 18 },
{ name: '22', age: 18 }
]
// 等到這里再清空枣宫,就不閃爍了婆誓。
retArray.length = 0
retArray.push(...newArray)
}, 1000)
var 和 let、const
ES6 新增了 let 和 const也颤,那么我們應(yīng)該如何選擇呢洋幻?
簡單的說,var不必繼續(xù)使用了翅娶。
let 和 const 的最大區(qū)別就是,前者是定義“變量”的锐借,后者是定義“常量”的穴店。
可能你會覺得奇怪,上面的代碼都是用const定義的渠啊,但是后續(xù)代碼都是各種改呀,怎么就常量了权旷?其實const判斷的是替蛉,地址是否改變,只要地址不變就可以拄氯。
對于基礎(chǔ)類型躲查,值變了地址就變了;而對于引用類型來說译柏,改屬性值的話镣煮,對象地址是不會發(fā)生變化的。
而 const 的這個特點整合可以用于保護 reactive 的實例鄙麦。由Vue的機制決定典唇,reactive的實例的地址是不可以改變的,變了的話模板就不會自動更新胯府,const可以確保地址不變介衔,變了會報錯(開發(fā)階段需要eslint支持)。
于是const和reactive(包括 ref 等)就成了絕配骂因。
源碼:
GitHub總是上不去炎咖,所以搬到gitee了。
https://gitee.com/naturefw/nf-vue-cdn/tree/master/cdn/project-compositionapi
在線演示:
https://naturefw.gitee.io/nf-vue-cdn/cdn/project-compositionapi/