全面了解Vue3的 reactive 和相關(guān)函數(shù)(下)

淺層響應(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的打印結(jié)果

shallowReactive 也是用 Proxy 實現(xiàn)響應(yīng)性的,而單獨使用contacts屬性并沒有響應(yīng)性露乏,因為 shallowReactive 是淺層代理碧浊,所以不會讓嵌套對象獲得響應(yīng)性。

注意:objectShallowReactive.contacts.QQ = 123 施无,這樣修改屬性也是沒有響應(yīng)性的辉词。

單獨使用的屬性的形式:

shallowReactive的屬性

嵌套對象和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é)果:

markRaw的打印結(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é)果:

object的readonly
  • Handler续誉,明顯攔截的函數(shù)變少了,set的參數(shù)也變少了初肉,點進去看源碼酷鸦,也僅僅只有一行返回警告的代碼,這樣實現(xiàn)攔截設(shè)置屬性的操作牙咏。
  • Target臼隔,指向object。

運行結(jié)果:

reactive的readonly
  • 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/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寒波,一起剝皮案震驚了整個濱河市乘盼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俄烁,老刑警劉巖绸栅,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異页屠,居然都是意外死亡粹胯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門卷中,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矛双,“玉大人,你說我怎么就攤上這事蟆豫∫楹觯” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵十减,是天一觀的道長栈幸。 經(jīng)常有香客問我愤估,道長,這世上最難降的妖魔是什么速址? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任玩焰,我火速辦了婚禮,結(jié)果婚禮上芍锚,老公的妹妹穿的比我還像新娘昔园。我一直安慰自己,他們只是感情好并炮,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布默刚。 她就那樣靜靜地躺著,像睡著了一般逃魄。 火紅的嫁衣襯著肌膚如雪荤西。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天伍俘,我揣著相機與錄音邪锌,去河邊找鬼。 笑死癌瘾,一個胖子當著我的面吹牛觅丰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柳弄,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼舶胀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碧注?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤糖赔,失蹤者是張志新(化名)和其女友劉穎萍丐,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體放典,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡逝变,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奋构。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壳影。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弥臼,靈堂內(nèi)的尸體忽然破棺而出宴咧,到底是詐尸還是另有隱情,我是刑警寧澤径缅,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布掺栅,位于F島的核電站烙肺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏氧卧。R本人自食惡果不足惜桃笙,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沙绝。 院中可真熱鬧搏明,春花似錦、人聲如沸闪檬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谬以。三九已至强饮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間为黎,已是汗流浹背邮丰。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铭乾,地道東北人剪廉。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像炕檩,于是被迫代替她去往敵國和親斗蒋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容