今年,對於從事前端開發的同窗而言,非常期待的一件事就是 Vue3.0
的發佈。可是,Vue3.0
離發佈仍是有點時間的,而且正式發佈也不表明咱們就立刻就能夠用於業務開發。它還須要完善相應的生態工具。不過正式使用是一碼事,咱們本身玩又是一碼事(hh)。javascript
Vue3.0
特意準備了一個嚐鮮版的項目供你們體驗 Vue3.0
即將會出現的一些 API
,例如 setup
、reactive
、toRefs
、readonly
等等, 順帶附上Composition API文檔 的地址,還沒看過的同窗趕忙去 Get
,別等到發佈才知道(笨鳥要先飛,聰明鳥那更要先飛是吧)。html
一樣地,我也 Clone
了下來玩了一會,對這個 reactive API
頗感興趣。因此,今天咱們就來看看 reactive API
是什麼(定義)?怎麼實現的(源碼實現)?前端
reactive API
的定義爲傳入一個對象並返回一個基於原對象的響應式代理,即返回一個 Proxy
,至關於 Vue2x
版本中的 Vue.observer
。vue
首先,咱們須要知道在 Vue3.0
中完全廢掉了原先的 Options API
,而改用 Composition API
,簡易版的 Composition API
看起來會是這樣的:java
setup() { const state = reactive({ count: 0, double: computed(() => state.count * 2) }) function increment() { state.count++ } return { state, increment } }
能夠看到,沒有了咱們熟悉的data
、computed
、methods
等等。看起來,彷佛有點 React
風格,這個提出確實當時社區中引起了不少討論,說Vue
愈來愈像React
....不少人並非很能接受,具體細節你們能夠去閱讀 RFC 的介紹。react
回到本篇文章所關注的,很明顯 reactive API
是對標 data
選項,那麼相比較 data
選項有哪些優勢?webpack
首先,在 Vue 2x
中數據的響應式處理是基於 Object.defineProperty()
的,可是它只會偵聽對象的屬性,並不能偵聽對象。因此,在添加對象屬性的時候,一般須要這樣:git
// vue2x添加屬性 Vue.$set(object, 'name', wjc)
reactive API
是基於 ES2015 Proxy
實現對數據對象的響應式處理,即在 Vue3.0
能夠往對象中添加屬性,而且這個屬性也會具備響應式的效果,例如:github
// vue3.0中添加屬性 object.name = 'wjc'
使用 reactive API
須要注意的是,當你在 setup
中返回的時候,須要經過對象的形式,例如:web
export default { setup() { const pos = reactive({ x: 0, y: 0 }) return { pos: useMousePosition() } } }
或者,藉助 toRefs API
包裹一下導出,這種狀況下咱們就可使用展開運算符或解構,例如:
export default { setup() { let state = reactive({ x: 0, y: 0 }) state = toRefs(state) return { ...state } } }
toRefs() 具體作了什麼,接下來會和 reactive 一塊兒講解
首先,相信你們都有所耳聞,Vue3.0
用 TypeScript
重構了。因此,你們可能會覺得此次會看到一堆 TypeScript
的類型之類的。出於各類考慮,本次我只是講解編譯後,轉爲 JS 的源碼實現(沒啥子門檻,你們放心 hh)。
1.先來看看 reactive
函數的實現:
function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (readonlyToRaw.has(target)) { return target; } // target is explicitly marked as readonly by user if (readonlyValues.has(target)) { return readonly(target); } if (isRef(target)) { return target; } return createReactiveObject(target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers); }
能夠,看到先有 3 個邏輯判斷,對 readonly
、readonlyValues
、isRef
分別進行了判斷。咱們先不看這些邏輯,一般咱們定義 reactive
會直接傳入一個對象。因此會命中最後的邏輯 createReactiveObject()
。
2.那咱們轉到 createReactiveObject()
的定義:
function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) { if (!isObject(target)) { if ((process.env.NODE_ENV !== 'production')) { console.warn(`value cannot be made reactive: ${String(target)}`); } return target; } // target already has corresponding Proxy let observed = toProxy.get(target); if (observed !== void 0) { return observed; } // target is already a Proxy if (toRaw.has(target)) { return target; } // only a whitelist of value types can be observed. if (!canObserve(target)) { return target; } const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers; observed = new Proxy(target, handlers); toProxy.set(target, observed); toRaw.set(observed, target); return observed; }
createReactiveObject()
傳入了四個參數,它們分別扮演的角色:
target
是咱們定義 reactive
時傳入的對象toProxy
是一個空的 WeakSet
。toProxy
是一個空的 WeakSet
。baseHandlers
是一個已經定義好 get
和 set
的對象,它看起來會是這樣:const baseHandlers = { get(target, key, receiver) {}, set(target, key, value, receiver) {}, deleteProxy: (target, key) {}, has: (target, key) {}, ownKey: (target) {} };
collectionHandlers
是一個只包含 get 的對象。而後,進入 createReactiveObject()
, 一樣地,一些分支邏輯咱們此次不會去分析。
看源碼時須要保持的一個日常心,先看主邏輯
因此,咱們會命中最後的邏輯,即:
const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers; observed = new Proxy(target, handlers); toProxy.set(target, observed); toRaw.set(observed, target);
它首先判斷 collectionTypes
中是否會包含咱們傳入的 target
的構造函數,而 collectionTypes
是一個 Set
集合,主要包含 Set
, Map
, WeakMap
, WeakSet
等四種集合的構造函數。
若是 collectionTypes
包含它的構造函數,那麼將 handlers
賦值爲只有 get
的 collectionHandlers
對象,不然,賦值爲 baseHandlers
對象。
這二者的區別就在於前者只有 get,很顯然這個是留給不須要派發更新的變量定義的,例如咱們熟悉的 props 它就只實現了 get。
而後,將 target
和 handlers
傳入 Proxy
,做爲參數實例化一個 Proxy
對象。這也是咱們看到一些文章常談的 Vue3.0
用 ES2015 Proxy
取代了 Object.defineProperty
。
最後的兩個邏輯,也是很是重要,toProxy()
將已經定義好 Proxy
對象的 target
和 對應的 observed
做爲鍵值對塞進 toProxy
這個 WeakMap
中,用於下次若是存在相同引用的 target 須要 reactive,會命中前面的分支邏輯,返回定義以前定義好的 observed
,即:
// target already has corresponding Proxy target 是已經有相關的 Proxy 對象 let observed = toProxy.get(target); if (observed !== void 0) { return observed; }
而 toRaw()
則是和 toProxy
相反的鍵值對存入,用於下次若是傳進的 target
已是一個 Proxy
對象時,返回這個 target
,即:
// target is already a Proxy target 已是一個 Proxy 對象 if (toRaw.has(target)) { return target; }
前面講了使用 reactive
須要關注的點,說起 toRefs
可讓咱們方便地使用解構和展開運算符,實際上是最近 Vue3.0 issue
也有大神講解過這方面的東西。有興趣的同窗能夠移步 When it's really needed to use toRefs
in order to retain reactivity of reactive
value瞭解。
我當時也湊了一下熱鬧,以下圖:
能夠看到,toRefs
是在原有 Proxy
對象的基礎上,返回了一個普通的帶有 get
和 set
的對象。這樣就解決了 Proxy
對象遇到解構和展開運算符後,失去引用的狀況的問題。
好了,對於 reactive API
的定義和大體的源碼實現就如上面文章中描述的。而分支的邏輯,你們能夠自行走不一樣的 case
去閱讀。固然,須要說的是此次的源碼只是嚐鮮版的,不排除以後正式的會作諸多優化,可是主體確定是保持不變的。
推薦閱讀下一篇《4k+ 字分析 Vue 3.0 響應式原理(依賴收集和派發更新)》
寫做不易,若是你以爲有收穫的話,能夠帥氣三連擊!!!