Vue中怎樣把數(shù)據(jù)包裝成reactive從而實現(xiàn)MDV效果,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
創(chuàng)新互聯(lián)是一家業(yè)務(wù)范圍包括IDC托管業(yè)務(wù),雅安服務(wù)器托管、主機租用、主機托管,四川、重慶、廣東電信服務(wù)器租用,服務(wù)器托管,成都網(wǎng)通服務(wù)器托管,成都服務(wù)器租用,業(yè)務(wù)范圍遍及中國大陸、港澳臺以及歐美等多個國家及地區(qū)的互聯(lián)網(wǎng)數(shù)據(jù)服務(wù)公司。
先說明一下什么叫 reactive,簡單來說,就是將數(shù)據(jù)包裝成一種可觀測的類型,當數(shù)據(jù)產(chǎn)生變更的時候,我們能夠感知到。
而 Vue 的相關(guān)實現(xiàn)代碼全部都在 core/observer
目錄下,而要自行閱讀的話,建議從 core/instance/index.js
中開始。
在開始講 reactive 的具體實現(xiàn)之前,先說說幾個對象:Watcher、Dep、Observer。
Watcher 是 vue 實現(xiàn)的一個用于觀測數(shù)據(jù)的對象,具體實現(xiàn)在 core/observer/watcher.js
中。
這個類主要是用來觀察方法/表達式
中引用到的數(shù)據(jù)(數(shù)據(jù)需要是 reative 的,即 data 或者 props)變更,當變更后做出相應(yīng)處理。先看一下 Watcher 這個類的入?yún)ⅲ?/p>
vm: Component,expOrFn: string | Function,cb: Function,options?: Object
解釋一下這幾個入?yún)⑹歉陕锏模?/p>
vm:當前這個 watcher 所屬的 VueComponent。
expOrFn:需要監(jiān)聽的 方法/表達式。舉個例子:VueComponent 的 render function,或者是 computed 的 getter 方法,再或者是abc.bbc.aac
這種類型的字符串(由于 vue 的 parsePath 方法是用 split('.') 來做的屬性分割,所以不支持abc['bbc']
)。expOrFn 如果是方法,則直接賦值給 watcher 的 getter 屬性,如果是表達式,則會轉(zhuǎn)換成方法再給 getter。
cb:當 getter 中引用到的 data 發(fā)生改變的時候,就會觸發(fā)該回調(diào)。
options:額外參數(shù),可以傳入的參數(shù)為包括deep
、user
,lazy
,sync
,這些值默認都是為 false。
deep 如果為 true,會對 getter 返回的對象再做一次深度遍歷,進行進一步的依賴收集。
user 是用于標記這個監(jiān)聽是否由用戶通過 $watch 調(diào)用的。
lazy 用于標記 watcher 是否為懶執(zhí)行,該屬性是給 computed data 用的,當 data 中的值更改的時候,不會立即計算 getter 獲取新的數(shù)值,而是給該 watcher 標記為dirty
,當該 computed data 被引用的時候才會執(zhí)行從而返回新的 computed data,從而減少計算量。
sync 則是表示當 data 中的值更改的時候,watcher 是否同步更新數(shù)據(jù),如果是 true,就會立即更新數(shù)值,否則在 nextTick 中更新。
了解了入?yún)⑹怯脕砀陕锏闹螅簿突旧现?Watcher 這個對象干了啥。
Dep 則是 vue 實現(xiàn)的一個處理依賴關(guān)系的對象,具體實現(xiàn)在 core/observer/dep.js
中,代碼量相當少,很容易理解。
Dep 主要起到一個紐帶的作用,就是連接 reactive data 與 watcher,每一個 reactive data 的創(chuàng)建,都會隨著創(chuàng)建一個 dep 實例。參見 observer/index.js 中的defineReactive
方法,精簡的 defineReactive 方法如下。
function defineReactive(obj, key, value) { const dep = new Dep(); Object.defineProperty(obj, key, { get() { if (Dep.target) { dep.depend(); } return value } set(newValue) { value = newValue; dep.notify(); } })}
創(chuàng)建完 dep 實例后,就會給該 data 注入 getter 和 setter 的邏輯,當該 data 被引用的時候,就會觸發(fā) getter,而什么時候 data 會被引用呢?就是在 watcher 執(zhí)行 getter 的時候,而當 watcher 執(zhí)行 getter 的時候,watcher 會被塞入 Dep.target,然后通過調(diào)用 dep.depend() 方法,這個數(shù)據(jù)的 dep 就和 watcher 創(chuàng)建了連接。
創(chuàng)建連接之后,當 data 被更改,觸發(fā)了 setter 邏輯。然后就可以通過 dep.notify() 通知到所有與 dep 創(chuàng)建了關(guān)聯(lián)的 watcher。從而讓各個 watcher 做出響應(yīng)。
比如我 watch 了一個 data ,并且在一個 computed data 中引用了同一個 data。再同時,我在 template 中也有顯式引用了這個 data,那么此時,這個 data 的 dep 里就關(guān)聯(lián)了三個 watcher,一個是 render function 的 watcher,一個是 computed 的 watcher,一個是用戶自己調(diào)用 $watch 方法創(chuàng)建的 watcher。當 data 發(fā)生更改后,這個 data 的 dep 就會通知到這三個 watcher 做出相應(yīng)處理。
Observer 可以將一個 plainObject 或者 array 變成 reactive 的。代碼很少,就是遍歷 plainObject 或者 array,對每一個鍵值調(diào)用defineReactive
方法。
以上三個類介紹完了,基本上對 vue reactive 的實現(xiàn)應(yīng)該有個模糊的認識,接下來,就結(jié)合實例講一下整個流程。
在 vue 實例化的時候,會先調(diào)用 initData,再調(diào)用 initComputed,最后再調(diào)用 mountComponent 創(chuàng)建 render function 的 watcher。從而完成一個 VueComponent 的數(shù)據(jù) reactive 化。
initData 方法在 core/instance/state.js 中,而這個方法里大部分都是做一些判斷,比如防止 data 里有跟 methods 里重復(fù)的命名之類的。核心其實就一行代碼:
observe(data, true)
而這個 observe 方法干的事就是創(chuàng)建一個 Observer 對象,而 Observer 對象就像我上面說的,對 data 進行遍歷,并且調(diào)用 defineReactive 方法。
就會使用 data 節(jié)點創(chuàng)建一個 Observer 對象,然后對 data 下的所有數(shù)據(jù),依次進行 reactive 的處理,也就是調(diào)用 defineReactive
方法。當執(zhí)行完 defineReactive 方法之后,data 里的每一個屬性,都被注入了 getter 以及 setter 邏輯,并且創(chuàng)建了 dep 對象。至此 initData 執(zhí)行完畢。
然后是 initComputed 方法。這個方法就是處理 vue 中 computed 節(jié)點下的數(shù)據(jù),遍歷 computed 節(jié)點,獲取 key 和 value,創(chuàng)建 watcher 對象,如果 value 是方法,實例化 watcher 的入?yún)?expOrFn 則為 value,否則是 value.get。
function initComputed (vm: Component, computed: Object) { ... const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] let getter = typeof userDef === 'function' ? userDef : userDef.get ... watchers[key] = new Watcher(vm, getter, noop, { lazy: true }) if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { ... } }}
我們知道 expOrFn 是可以為方法,也可以是字符串的。因此,通過上面的代碼我們發(fā)現(xiàn)了一種官方文檔里沒有說明的用法,比如我的 data 結(jié)構(gòu)如下
{ obj: { list: [{value: '123'}] } }
如果我們要在 template 中需要使用 list 中第一個節(jié)點的 value 屬性 值,就寫個 computed:
computed: { value: { get: 'obj.list.0.value' }}
然后在 template 中使用的時候,直接用{{ value }}
,這樣的話,就算 list 為空,也能保證不會報錯,類似于 lodash.get 的用法。OK,扯遠了,回到正題上。
創(chuàng)建完 watcher,就通過 Object.defineProperty 把 computed 的 key 掛載到 vm 上。并且在 getter 中添加以下邏輯
if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value
前面我有說過,computed data 的 watcher 是 lazy 的,當 computed data 中引用的 data 發(fā)生改變后,是不會立馬重新計算值的,而只是標記一下 dirty 為 true,然后當這個 computed data 被引用的時候,上面的 getter 邏輯就會判斷 watcher 是否為 dirty,如果是,就重新計算值。
而后面那一段watcher.depend
。則是為了收集 computed data 中用到的 data 的依賴,從而能夠?qū)崿F(xiàn)當 computed data 中引用的 data 發(fā)生更改時,也能觸發(fā)到 render function 的重新執(zhí)行。
depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } }
把 data 以及 computed 都初始化好之后,則創(chuàng)建一個 render function 的 watcher。邏輯如下:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean): Component { vm.$el = el ... callHook(vm, 'beforeMount') let updateComponent ... updateComponent = () => { vm._update(vm._render(), hydrating) } ... vm._watcher = new Watcher(vm, updateComponent, noop) if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm}
可以看到,創(chuàng)建 watcher 時候的入?yún)?expOrFn 為 updateComponent 方法,而 updateComponent 方法中則是執(zhí)行了 render function。而這個 watcher 不是 lazy 的,因此創(chuàng)建該 watcher 的時候,就會立馬執(zhí)行 render function 了,當執(zhí)行 render function 的時候。如果 template 中有使用 data,則會觸發(fā) data 的 getter 邏輯,然后執(zhí)行 dep.depend() 進行依賴收集,如果 template 中有使用 computed 的參數(shù),也會觸發(fā) computed 的 getter 邏輯,從而再收集 computed 的方法中引用的 data 的依賴。最終完成全部依賴的收集。
最后舉個例子:
{{ test }}
將 name 處理為 reactive,創(chuàng)建 dep 實例
將 test 綁到 vm,創(chuàng)建 test 的 watcher 實例 watch2,添加 getter 邏輯。
創(chuàng)建 render function 的 watcher 實例 watcher2,并且立即執(zhí)行 render function。
執(zhí)行 render function 的時候,觸發(fā)到 test 的 getter 邏輯,watcher1 及 watcher2 均與 dep 創(chuàng)建映射關(guān)系。
遍歷綁定的 watcher 列表,執(zhí)行 watcher.update()。
watcher1.dirty 置為為 true。
watcher2 重新執(zhí)行 render function,觸發(fā)到 test 的 getter,因為 watcher1.dirty 為 true,因此重新計算 test 的值,test 的值更新。
重渲染 view
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。