Vue3源碼終於發佈了!火燒眉毛的擼一下源碼,看看Vue3和Vue2到底有什麼區別。html
以前寫過兩篇Vue2響應式原理的文章:vue
Vue2的原理是:github
Vue3的響應式原理是用了proxy的方式來實現,優化了Vue2響應式存在的幾個問題,今天就從源碼來分析下vue3的響應式原理:typescript
(Vue3的源碼是使用ts開發的,須要你們提早學習ts相關知識)api
在Vue3中咱們想要建立一個響應式數據,要怎麼作呢?數組
查看下官方api咱們看到一段最基礎的示例代碼:app
<template>
<button @click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
})
function increment() {
state.count++
}
return {
state,
increment
}
}
}
</script>
複製代碼
示例中咱們發現使用reactive生成的state是響應對象,當state.count變化時,依賴state.count的template片斷和double都會相應的變化。函數
那麼reactive到底作了什麼使數據變成了響應對象呢?
####解析reactive
reactive和Vue2中的Vue.observable()相似,返回一個響應對象;reactive返回的響應對象主要用於頁面顯示,當響應對象改變時視圖會自動從新渲染,實現數據和視圖的雙向綁定。
此處解析代碼:
代碼結構很是清晰,咱們能夠很明白的看到reactive函數的入參必須是一個Object類型的數據,而返回則是一個UnwrapNestedRefs類型的對象(被解嵌套之後的響應式對象,後面會分析UnwrapNestedRefs),此處能夠直接簡單的理解爲返回了一個響應式對象。
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// 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)
}
return createReactiveObject(
target,
// 弱引用的map結構,用於保存原始數據對應的響應式數據
rawToReactive,
// 弱引用的map結構,用於保存響應式數據對應的原始數據
reactiveToRaw,
mutableHandlers,
mutableCollectionHandlers
)
}
複製代碼
reactive中關鍵點在於調用createReactiveObject方法,經過該方法返回傳入對象的對應的響應式對象。在createReactiveObject方法中調用new proxy,生成一個代理對象,將該代理對象做爲最終的響應式對象並返回。
function createReactiveObject( target: any, // 保存原始數據的weakMap toProxy: WeakMap<any, any>, // 保存響應式數據的weakMap toRaw: WeakMap<any, any>, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) {
// reactive的數據只能是Object類型
if (!isObject(target)) {
if (__DEV__) {
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
}
// 定義new proxy中的處理函數handlers
// 集合類型的對象使用 collectionHandlers.ts中的 mutableCollectionHandlers
// 其餘類型的對象使用 baseHandlers.ts中的 mutableHandlers
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
// 調用new proxy,生成一個代理對象,將該代理對象做爲最終的響應式對象並返回
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
return observed
}
複製代碼
其中調用new Proxy傳入的handler是響應式的關鍵,集合類型的對象使用 collectionHandlers.ts中的 mutableCollectionHandlers做爲new Proxy的handler;其餘類型的對象使用 baseHandlers.ts中的 mutableHandlers做爲new Proxy的handler。
baseHandlers.ts中的 mutableHandlers中使用createGetter代理對象的get方法、set代理對象的set方法。
其中createGetter方法中作了四件事:一、獲取數據的值;二、判斷數據是否已經進行過響應式處理;三、使用track方法進行依賴收集;四、對數據的每個Object類型的屬性進行reactive遞歸
function createGetter(isReadonly: boolean) {
return function get(target: any, key: string | symbol, receiver: any) {
// 獲取數據的值
const res = Reflect.get(target, key, receiver)
// 已經通過響應式處理的ref數據則直接返回
if (typeof key === 'symbol' && builtInSymbols.has(key)) {
return res
}
if (isRef(res)) {
return res.value
}
// 未通過響應式處理的數據收集其依賴
track(target, OperationTypes.GET, key)
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
// circular dependency
readonly(res)
: reactive(res)
: res
}
}
複製代碼
set方法中作了三件事:一、將set行爲更新到原屬數據對象上;二、判斷代理數據中有沒有相應的key,沒有則作新增處理,有則作修改處理;三、調用trigger方法,觸發其依賴;
function set( target: any, key: string | symbol, value: any, receiver: any ): boolean {
value = toRaw(value)
// 判斷代理對象中是否有這個key。如有的話就進行修改,若沒有則新增
const hadKey = hasOwn(target, key)
const oldValue = target[key]
// 原值是ref類型,新值不是,則直接賦值,由於原值已經被監聽了set觸發trigger。此處避免重複觸發。
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
// 將set行爲更新到原屬數據對象上
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// 若是target是該代理數據相對應的原始數據才作處理,若是targer只是代理數據相對應的原始數據原型鏈上的數據則不作操做
if (target === toRaw(receiver)) {
/* istanbul ignore else */
if (__DEV__) {
const extraInfo = { oldValue, newValue: value }
if (!hadKey) {
trigger(target, OperationTypes.ADD, key, extraInfo)
} else if (value !== oldValue) {
trigger(target, OperationTypes.SET, key, extraInfo)
}
} else {
if (!hadKey) {
// 代理數據中沒有響應的key,則作新增處理,並觸發其依賴
trigger(target, OperationTypes.ADD, key)
} else if (value !== oldValue) {
// 代理數據中有響應的key,則作修改處理,並觸發其依賴
trigger(target, OperationTypes.SET, key)
}
}
}
return result
}
複製代碼
不作過多解釋,你們直接看這裏吧: vue3響應式源碼解析-Reactive篇-collectionHandlers
從上述解析中咱們一直看到一個概念就是ref,那麼在vue3中ref究竟是什麼呢?vue3中主要的是ref()函數,ref()函數接受一個基本類型的數據,並返回一個響應式的ref對象。瞭解ref()函數以前須要瞭解下面兩個基本概念:Ref接口、UnwrapNestedRefs。
對Ref接口的定義如 ref.ts:
// Ref接口
export interface Ref<T> {
// 惟一標識位,標識對象是一個ref對象
[refSymbol]: true
// 存放數據,是基本類型數據真正存在的地方,UnwrapNestedRefs表示被接嵌套之後的ref類型的對象
value: UnwrapNestedRefs<T>
}
複製代碼
以前分析reactive時又講到過reactive返回的是一個UnwrapNestedRefs類型的數據,且上面說Ref接口中的value也是UnwrapNestedRefs類型的數據。
export type UnwrapNestedRefs<T> = T extends Ref<any> ? T : UnwrapRef<T>
複製代碼
由上面代碼可見UnwrapNestedRefs是Ref類型的數據,或者通過UnwrapRef接嵌套之後的數據。 UnwrapRef的定義以下:
// Recursively unwraps nested value bindings.
export type UnwrapRef<T> = {
ref: T extends Ref<infer V> ? UnwrapRef<V> : T
array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
object: { [K in keyof T]: UnwrapRef<T[K]> }
stop: T
}[T extends Ref<any>
? 'ref'
: T extends Array<any>
? 'array'
: T extends BailTypes
? 'stop' // bail out on types that shouldn't be unwrapped
: T extends object ? 'object' : 'stop']
複製代碼
這個接嵌套寫的很是巧妙,其中經過infer V推斷出類型進行進一步的遞歸解構(其中infer語句表示在 extends
條件語句中待推斷的類型變量:infer定義),也就是說UnwrapNestedRefs只能是ref類型或者其餘任意類型的的對象,可是不能是嵌套了ref類型的對象,即不能是這樣Ref<Ref>
這樣Array<Ref>
或者這樣 { [key]: Ref }
嵌套型的ref類型對象。
// 判斷數據是否是對象,是對象的話調用reactive()函數將其變爲響應式數據,不是對象的話直接返回
const convert = (val: any): any => (isObject(val) ? reactive(val) : val)
// ref函數,接受一個原始數據,返回其響應式的Ref類型的對象
export function ref<T>(raw: T): Ref<T> {
raw = convert(raw)
const v = {
// 添加惟一標識位,標識對象是一個ref對象
[refSymbol]: true,
get value() {
// 依賴收集
track(v, OperationTypes.GET, '')
// 返回get結果
return raw
},
set value(newVal) {
// 對新值進行響應式處理
raw = convert(newVal)
// 依賴觸發
trigger(v, OperationTypes.SET, '')
}
}
return v as Ref<T>
}
複製代碼
此處ref()主要解決基本類型的數據沒法變成響應式數據的問題。 ref和reactive的功能類似,ref用於將基本類型的數據轉爲響應式數據;reactive用於將對象類型的數據轉爲響應式數據