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

溫故而知新,同樣的知識(shí)點(diǎn)族操,再次學(xué)習(xí)的時(shí)候也可以有新的認(rèn)識(shí),這里把 reactive 和相關(guān)函數(shù)總體介紹一下。
從 ES6 的 Proxy 開(kāi)始色难,到Vue3提供的各種函數(shù)泼舱,盡量的把自己的理解融入進(jìn)來(lái),當(dāng)然目前水平限制枷莉,無(wú)法做到非常深入的程度娇昙。以后有更深的了解之后還會(huì)有新的見(jiàn)解。

ES6的Proxy

Proxy 是 ES6 提供的一個(gè)可以攔截對(duì)象基礎(chǔ)操作的代理笤妙。因?yàn)?reactive 采用 Proxy 代理的方式冒掌,實(shí)現(xiàn)引用類(lèi)型的響應(yīng)性,所以我們先看看 Proxy 的基礎(chǔ)使用方法蹲盘,以便于我理解 reactive 的結(jié)構(gòu)股毫。

我們先來(lái)定義一個(gè)函數(shù),了解一下 Proxy 的基本使用方式:

// 定義一個(gè)函數(shù)召衔,傳入對(duì)象原型铃诬,然后創(chuàng)建一個(gè)Proxy的代理
const myProxy = (_target) => {
  // 定義一個(gè) Proxy 的實(shí)例
  const proxy = new Proxy(_target, {
    // 攔截 get 操作
    get: function (target, key, receiver) {
      console.log(`getting ${key}!`, target[key])
      // 用 Reflect 調(diào)用原型方法
      return Reflect.get(target, key, receiver)
    },
    // 攔截 set 操作
    set: function (target, key, value, receiver) {
      console.log(`setting ${key}:${value}!`)
      // 用 Reflect 調(diào)用原型方法
      return Reflect.set(target, key, value, receiver)
    }
  })
  // 返回實(shí)例
  return proxy
}

// 使用方法,是不是和reactive有點(diǎn)像苍凛?
const testProxy = myProxy({
  name: 'jyk',
  age: 18,
  contacts: {
    QQ: 11111,
    phone: 123456789
  }
})
console.log('自己定義的Proxy實(shí)例:')
console.log(testProxy)
// 測(cè)試攔截情況
testProxy.name = '新的名字' // set操作 
console.log(testProxy.name) // get 操作

Proxy 有兩個(gè)參數(shù) target 和 handle氧急。

  • target:要代理的對(duì)象,也可以是數(shù)組毫深,但是不能是基礎(chǔ)類(lèi)型吩坝。
  • handler:設(shè)置要攔截的操作,這里攔截了 set 和 get 操作哑蔫,當(dāng)然還可以攔截其他操作钉寝。

我們先來(lái)看一下運(yùn)行結(jié)果:


自己寫(xiě)的 Proxy 實(shí)例的運(yùn)行結(jié)果
  • Handler 可以看到我們寫(xiě)的攔截函數(shù) get 和 set;
  • Target 可以看到對(duì)象原型闸迷。

注意:這里只是實(shí)現(xiàn)了 get 和 set 的攔截嵌纲,并沒(méi)有實(shí)現(xiàn)數(shù)據(jù)的雙向綁定,模板也不會(huì)自動(dòng)更新內(nèi)容腥沽,Vue內(nèi)部做了很多操作才實(shí)現(xiàn)了模板的自動(dòng)更新功能逮走。

用 Proxy 給 reactive 套個(gè)娃,會(huì)怎么樣今阳?

有個(gè)奇怪的地方师溅,既然 Proxy 可以實(shí)現(xiàn)對(duì) set 等操作的攔截,那么 reactive 為啥不返回一個(gè)可以監(jiān)聽(tīng)的鉤子呢盾舌?為啥要用 watch 來(lái)實(shí)現(xiàn)監(jiān)聽(tīng)的工作墓臭?

為啥會(huì)這么想?因?yàn)榭吹搅?Vuex4.0 的設(shè)計(jì)妖谴,明明已經(jīng)把 state 整體自動(dòng)變成了 reactive 的形式窿锉,那么為啥還非得在 mutations 里寫(xiě)函數(shù),實(shí)現(xiàn) set 操作呢?好麻煩的樣子嗡载。

外部直接對(duì) reactive 進(jìn)行操作窑多,然后 Vuex 內(nèi)部監(jiān)聽(tīng)一下,這樣大家不就都省事了嗎洼滚?要實(shí)現(xiàn)插件功能埂息,還是跟蹤功能,不都是可以自動(dòng)實(shí)現(xiàn)了嘛判沟。

所以我覺(jué)得還是可以套個(gè)娃的耿芹。

實(shí)現(xiàn)模板的自動(dòng)刷新

本來(lái)以為上面那個(gè) myProxy 函數(shù),傳入一個(gè) reactive 之后挪哄,就可以自動(dòng)實(shí)現(xiàn)更新模板的功能了吧秕,結(jié)果模板沒(méi)理我。

這不對(duì)呀迹炼,我只是監(jiān)聽(tīng)了一下砸彬,不是又交給 reactive 了嗎?為啥模板不理我斯入?

經(jīng)過(guò)各種折騰砂碉,終于找到了原因,于是函數(shù)改成了這樣:

  /**
   * 用 Proxy定義一個(gè) reactive 的套娃刻两,實(shí)現(xiàn)可以監(jiān)聽(tīng)任意屬性變化的目的增蹭。(不包含嵌套對(duì)象的屬性)
   * @param {*} _target  要攔截的目標(biāo)
   * @param {*} callback 屬性變化后的回調(diào)函數(shù)
   */
  const myReactive = (_target, callback) => {
    let _change = (key, value) => {console.log('內(nèi)部函數(shù)')}
    const proxy = new Proxy(_target, {
      get: function (target, key, receiver) {
        if (typeof key !== 'symbol') {
          console.log(`getting ${key}!`, target[key])
        } else {
          console.log('getting symbol:', key, target[key])
        }
        // 調(diào)用原型方法
        return Reflect.get(target, key, receiver)
      },
      set: function (target, key, value, receiver) {
        console.log(`setting ${key}:${value}!`)
        // 源頭監(jiān)聽(tīng)
        if (typeof callback === 'function') {
          callback(key, value)
        }
        // 任意位置監(jiān)聽(tīng)
        if (typeof _target.__watch === 'function') {
          _change(key, value)
        }
        // 調(diào)用原型方法
        return Reflect.set(target, key, value, target)  // 這里有變化,最后一個(gè)參數(shù)改成 target
      }
    })
    // 實(shí)現(xiàn)任意位置的監(jiān)聽(tīng)磅摹,
    proxy.__watch = (callback) => {
      if (typeof callback === 'function') {
        _change = callback
      }
    }
    // 返回實(shí)例
    return proxy
  }

代碼稍微多了一些滋迈,我們一塊一塊看。

  • get
    這里要做一下 symbol 的判斷户誓,否則會(huì)報(bào)錯(cuò)饼灿。好吧,其實(shí)我們似乎不需要 console.log帝美。

  • set
    這里改了一下最后一個(gè)參數(shù)碍彭,這樣模板就可以自己更新了。

  • 設(shè)置 callback 函數(shù)悼潭,實(shí)現(xiàn)源頭監(jiān)聽(tīng)
    設(shè)置一個(gè)回調(diào)函數(shù)庇忌,才能在攔截到set操作的時(shí)候,通知外部的調(diào)用者女责。只是這樣只適合于定義實(shí)例的地方漆枚。那么接收參數(shù)的地方怎么辦呢?

調(diào)用方法如下:

    // 定義一個(gè)攔截reactive的Proxy
    // 并且實(shí)現(xiàn)源頭的監(jiān)聽(tīng)
    const myProxyReactive = myReactive(retObject,
      ((key, value) =>{
        console.log(`ret外部獲得通知:${key}:${value}`)
      })
    )

這樣我們就可以在回調(diào)函數(shù)里面得到修改的屬性名稱抵知,以及屬性值。

這樣我們做狀態(tài)管理的時(shí)候,是不是就不用特意去寫(xiě) mutations 里面的函數(shù)了呢刷喜?

  • 內(nèi)部設(shè)置一個(gè)鉤子函數(shù)
    設(shè)置一個(gè) _change() 鉤子函數(shù)残制,這樣接收參數(shù)的地方,可以通過(guò)這個(gè)鉤子來(lái)得到變化的通知掖疮。

調(diào)用方法如下:

   // 任意位置的監(jiān)聽(tīng)
    myProxyReactive.__watch((key, value) => {
      console.log(`任意位置的監(jiān)聽(tīng):${key}:${value}`)
    })

只是好像哪里不對(duì)的樣子初茶。
首先這個(gè)鉤子沒(méi)找到合適的地方放,目前放在了原型對(duì)象上面浊闪,就是說(shuō)破壞了原型對(duì)象的結(jié)構(gòu)恼布,這個(gè)似乎會(huì)有些影響。

然后搁宾,接收參數(shù)的地方折汞,不是可以直接得到修改的情況嗎?是否還需要做這樣的監(jiān)聽(tīng)盖腿?

最后爽待,好像沒(méi)有 watch 的 deep 監(jiān)聽(tīng)來(lái)的方便,那么問(wèn)題又來(lái)了翩腐,為啥 Vuex 不用 watch 呢鸟款?或者悄悄的用了?

深層響應(yīng)式代理:reactive

說(shuō)了半天茂卦,終于進(jìn)入正題了何什。
reactive 會(huì)返回對(duì)象的響應(yīng)式代理,這種響應(yīng)式轉(zhuǎn)換是深層的等龙,可以影響所有的嵌套對(duì)象处渣。

注意:返回的是 object 的代理,他們的地址是相同的而咆,并沒(méi)有對(duì)object進(jìn)行clone(克禄舯取),所以修改代理的屬性值暴备,也會(huì)影響原object的屬性值悠瞬;同時(shí),修改原object的屬性值,也會(huì)影響reactive返回的代理的屬性值罩阵,只是代理無(wú)法攔截直接對(duì)原object的操作穿仪,所以模板不會(huì)有變化。

這個(gè)問(wèn)題并不明顯凌外,因?yàn)槲覀円话悴粫?huì)先定義一個(gè)object,然后再套上reactive涛浙,而是直接定義一個(gè) reactive康辑,這樣也就“不存在”原 object 了摄欲,但是我們要了解一下原理。

我們先定義一個(gè) reactive 實(shí)例疮薇,然后運(yùn)行看結(jié)果胸墙。

// js對(duì)象
const person = {
  name: 'jyk',
  age: 18,
  contacts: {
    QQ: 11111,
    phone: 123456789
  }
}
// person 的 reactive 代理 (驗(yàn)證地址是否相同)
const personReactive = reactive(person)
// js 對(duì)象 的 reactive 代理 (一般用法)
const objectReactive = reactive({
  name: 'jykReactive',
  age: 18,
  contacts: {
    QQ: 11111,
    phone: 123456789
  }
})

// 查看 reactive 實(shí)例結(jié)構(gòu)
console.log('reactive', objectReactive )

// 獲取嵌套對(duì)象屬性
const contacts = objectReactive .contacts
// 因?yàn)樯顚禹憫?yīng),所以依然有響應(yīng)性
console.log('contacts屬性:', contacts)
 
// 獲取簡(jiǎn)單類(lèi)型的屬性
let name = objectReactive.name 
// name屬性是簡(jiǎn)單類(lèi)型的按咒,所以失去響應(yīng)性
console.log('name屬性:', name) 

運(yùn)行結(jié)果:


reactive的打印結(jié)果
  • Handler:可以看到 Vue 除重寫(xiě) set 和 get 外迟隅,還重寫(xiě)了deleteProperty、has和ownKeys励七。

  • Target: 指向一個(gè)Object智袭,這是建立reactive實(shí)例時(shí)的對(duì)象。

屬性的結(jié)構(gòu):


reactive的屬性打印結(jié)果

然后再看一下兩個(gè)屬性的打印結(jié)果掠抬,因?yàn)?contacts 屬性是嵌套的對(duì)象吼野,所以單獨(dú)拿出來(lái)也是具有響應(yīng)性的。

而 name 屬性由于是 string 類(lèi)型剿另,所以單獨(dú)拿出來(lái)并不會(huì)自動(dòng)獲得響應(yīng)性箫锤,如果單獨(dú)拿出來(lái)還想保持響應(yīng)性的話,可以使用toRef雨女。

注意:如果在模板里面使用{{personReactive.name}}的話谚攒,那么也是有響應(yīng)性的,因?yàn)檫@種用法是獲得對(duì)象的屬性值氛堕,可以被Proxy代理攔截馏臭,所以并不需要使用toRef。
如果想在模板里面直接使用{{name}}并且要具有響應(yīng)性讼稚,這時(shí)才需要使用toRef括儒。

reactive 相關(guān)函數(shù)放在下一篇介紹。

源碼:

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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赠摇,隨后出現(xiàn)的幾起案子固逗,更是在濱河造成了極大的恐慌,老刑警劉巖藕帜,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烫罩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡洽故,警方通過(guò)查閱死者的電腦和手機(jī)贝攒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)时甚,“玉大人隘弊,你說(shuō)我怎么就攤上這事哈踱。” “怎么了长捧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵嚣鄙,是天一觀的道長(zhǎng)吻贿。 經(jīng)常有香客問(wèn)我串结,道長(zhǎng),這世上最難降的妖魔是什么舅列? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任肌割,我火速辦了婚禮,結(jié)果婚禮上帐要,老公的妹妹穿的比我還像新娘把敞。我一直安慰自己,他們只是感情好榨惠,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布奋早。 她就那樣靜靜地躺著,像睡著了一般赠橙。 火紅的嫁衣襯著肌膚如雪耽装。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天期揪,我揣著相機(jī)與錄音掉奄,去河邊找鬼。 笑死凤薛,一個(gè)胖子當(dāng)著我的面吹牛姓建,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缤苫,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼速兔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了活玲?” 一聲冷哼從身側(cè)響起涣狗,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翼虫,沒(méi)想到半個(gè)月后屑柔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡珍剑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年掸宛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片招拙。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唧瘾,死狀恐怖措译,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饰序,我是刑警寧澤领虹,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站求豫,受9級(jí)特大地震影響塌衰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝠嘉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一最疆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚤告,春花似錦努酸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至心褐,卻和暖如春舔涎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背檬寂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工终抽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桶至。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓昼伴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親镣屹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子圃郊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

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