您好,登錄后才能下訂單哦!
Vue中怎樣把數據包裝成reactive從而實現MDV效果,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
先說明一下什么叫 reactive,簡單來說,就是將數據包裝成一種可觀測的類型,當數據產生變更的時候,我們能夠感知到。
而 Vue 的相關實現代碼全部都在 core/observer
目錄下,而要自行閱讀的話,建議從 core/instance/index.js
中開始。
在開始講 reactive 的具體實現之前,先說說幾個對象:Watcher、Dep、Observer。
Watcher 是 vue 實現的一個用于觀測數據的對象,具體實現在 core/observer/watcher.js
中。
這個類主要是用來觀察方法/表達式
中引用到的數據(數據需要是 reative 的,即 data 或者 props)變更,當變更后做出相應處理。先看一下 Watcher 這個類的入參:
vm: Component,expOrFn: string | Function,cb: Function,options?: Object
解釋一下這幾個入參是干嘛的:
vm:當前這個 watcher 所屬的 VueComponent。
expOrFn:需要監聽的 方法/表達式。舉個例子:VueComponent 的 render function,或者是 computed 的 getter 方法,再或者是abc.bbc.aac
這種類型的字符串(由于 vue 的 parsePath 方法是用 split('.') 來做的屬性分割,所以不支持abc['bbc']
)。expOrFn 如果是方法,則直接賦值給 watcher 的 getter 屬性,如果是表達式,則會轉換成方法再給 getter。
cb:當 getter 中引用到的 data 發生改變的時候,就會觸發該回調。
options:額外參數,可以傳入的參數為包括deep
、user
,lazy
,sync
,這些值默認都是為 false。
deep 如果為 true,會對 getter 返回的對象再做一次深度遍歷,進行進一步的依賴收集。
user 是用于標記這個監聽是否由用戶通過 $watch 調用的。
lazy 用于標記 watcher 是否為懶執行,該屬性是給 computed data 用的,當 data 中的值更改的時候,不會立即計算 getter 獲取新的數值,而是給該 watcher 標記為dirty
,當該 computed data 被引用的時候才會執行從而返回新的 computed data,從而減少計算量。
sync 則是表示當 data 中的值更改的時候,watcher 是否同步更新數據,如果是 true,就會立即更新數值,否則在 nextTick 中更新。
了解了入參是用來干嘛的之后,也就基本上知道 Watcher 這個對象干了啥。
Dep 則是 vue 實現的一個處理依賴關系的對象,具體實現在 core/observer/dep.js
中,代碼量相當少,很容易理解。
Dep 主要起到一個紐帶的作用,就是連接 reactive data 與 watcher,每一個 reactive data 的創建,都會隨著創建一個 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(); } })}
創建完 dep 實例后,就會給該 data 注入 getter 和 setter 的邏輯,當該 data 被引用的時候,就會觸發 getter,而什么時候 data 會被引用呢?就是在 watcher 執行 getter 的時候,而當 watcher 執行 getter 的時候,watcher 會被塞入 Dep.target,然后通過調用 dep.depend() 方法,這個數據的 dep 就和 watcher 創建了連接。
創建連接之后,當 data 被更改,觸發了 setter 邏輯。然后就可以通過 dep.notify() 通知到所有與 dep 創建了關聯的 watcher。從而讓各個 watcher 做出響應。
比如我 watch 了一個 data ,并且在一個 computed data 中引用了同一個 data。再同時,我在 template 中也有顯式引用了這個 data,那么此時,這個 data 的 dep 里就關聯了三個 watcher,一個是 render function 的 watcher,一個是 computed 的 watcher,一個是用戶自己調用 $watch 方法創建的 watcher。當 data 發生更改后,這個 data 的 dep 就會通知到這三個 watcher 做出相應處理。
Observer 可以將一個 plainObject 或者 array 變成 reactive 的。代碼很少,就是遍歷 plainObject 或者 array,對每一個鍵值調用defineReactive
方法。
以上三個類介紹完了,基本上對 vue reactive 的實現應該有個模糊的認識,接下來,就結合實例講一下整個流程。
在 vue 實例化的時候,會先調用 initData,再調用 initComputed,最后再調用 mountComponent 創建 render function 的 watcher。從而完成一個 VueComponent 的數據 reactive 化。
initData 方法在 core/instance/state.js 中,而這個方法里大部分都是做一些判斷,比如防止 data 里有跟 methods 里重復的命名之類的。核心其實就一行代碼:
observe(data, true)
而這個 observe 方法干的事就是創建一個 Observer 對象,而 Observer 對象就像我上面說的,對 data 進行遍歷,并且調用 defineReactive 方法。
就會使用 data 節點創建一個 Observer 對象,然后對 data 下的所有數據,依次進行 reactive 的處理,也就是調用 defineReactive
方法。當執行完 defineReactive 方法之后,data 里的每一個屬性,都被注入了 getter 以及 setter 邏輯,并且創建了 dep 對象。至此 initData 執行完畢。
然后是 initComputed 方法。這個方法就是處理 vue 中 computed 節點下的數據,遍歷 computed 節點,獲取 key 和 value,創建 watcher 對象,如果 value 是方法,實例化 watcher 的入參 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 是可以為方法,也可以是字符串的。因此,通過上面的代碼我們發現了一種官方文檔里沒有說明的用法,比如我的 data 結構如下
{ obj: { list: [{value: '123'}] } }
如果我們要在 template 中需要使用 list 中第一個節點的 value 屬性 值,就寫個 computed:
computed: { value: { get: 'obj.list.0.value' }}
然后在 template 中使用的時候,直接用{{ value }}
,這樣的話,就算 list 為空,也能保證不會報錯,類似于 lodash.get 的用法。OK,扯遠了,回到正題上。
創建完 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 發生改變后,是不會立馬重新計算值的,而只是標記一下 dirty 為 true,然后當這個 computed data 被引用的時候,上面的 getter 邏輯就會判斷 watcher 是否為 dirty,如果是,就重新計算值。
而后面那一段watcher.depend
。則是為了收集 computed data 中用到的 data 的依賴,從而能夠實現當 computed data 中引用的 data 發生更改時,也能觸發到 render function 的重新執行。
depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } }
把 data 以及 computed 都初始化好之后,則創建一個 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}
可以看到,創建 watcher 時候的入參 expOrFn 為 updateComponent 方法,而 updateComponent 方法中則是執行了 render function。而這個 watcher 不是 lazy 的,因此創建該 watcher 的時候,就會立馬執行 render function 了,當執行 render function 的時候。如果 template 中有使用 data,則會觸發 data 的 getter 邏輯,然后執行 dep.depend() 進行依賴收集,如果 template 中有使用 computed 的參數,也會觸發 computed 的 getter 邏輯,從而再收集 computed 的方法中引用的 data 的依賴。最終完成全部依賴的收集。
最后舉個例子:
<template> <p>{{ test }}</p></template><script> export default { data() { return { name: 'cool' } }, computed: { test() { return this.name + 'test'; } } }</script>
將 name 處理為 reactive,創建 dep 實例
將 test 綁到 vm,創建 test 的 watcher 實例 watch2,添加 getter 邏輯。
創建 render function 的 watcher 實例 watcher2,并且立即執行 render function。
執行 render function 的時候,觸發到 test 的 getter 邏輯,watcher1 及 watcher2 均與 dep 創建映射關系。
遍歷綁定的 watcher 列表,執行 watcher.update()。
watcher1.dirty 置為為 true。
watcher2 重新執行 render function,觸發到 test 的 getter,因為 watcher1.dirty 為 true,因此重新計算 test 的值,test 的值更新。
重渲染 view
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。