時(shí)間:2023-05-09 16:12:02 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)
時(shí)間:2023-05-09 16:12:02 來(lái)源:網(wǎng)站運(yùn)營(yíng)
Vue3響應(yīng)式系統(tǒng)源碼解析-Ref篇:閱讀本文需要有一定的TypeScript基礎(chǔ),要求不高,看過(guò)一遍T(mén)S的文檔即可。我們閱讀源碼的原因是什么?無(wú)非是1:學(xué)習(xí);2:更好的使用這個(gè)庫(kù)。如果只是想大致的了解下原理,倒不必花時(shí)間閱讀源碼,幾句話,幾張圖就能搞清楚,網(wǎng)上搜搜應(yīng)該就有很多。因此,閱讀源碼的過(guò)程一定是要對(duì)不明白的地方深入了解,肯定是很費(fèi)時(shí)間的。
ref
跟reactive
是整個(gè)源碼中的核心,通過(guò)這兩個(gè)方法創(chuàng)建了響應(yīng)式數(shù)據(jù)。要想完全吃透reactivity
,必須先吃透這兩個(gè)。Ref
類(lèi)型,我們先來(lái)看,它到底是個(gè)怎么樣的數(shù)據(jù)類(lèi)型。(為了更好的做解釋?zhuān)視?huì)調(diào)整源碼中的接口、類(lèi)型、函數(shù)等聲明順序,并會(huì)增加一些注釋方便閱讀)// 生成一個(gè)唯一key,開(kāi)發(fā)環(huán)境下增加描述符 'refSymbol'export const refSymbol = Symbol(__DEV__ ? 'refSymbol' : undefined)// 聲明Ref接口export interface Ref<T = any> { // 用此唯一key,來(lái)做Ref接口的一個(gè)描述符,讓isRef函數(shù)做類(lèi)型判斷 [refSymbol]: true // value值,存放真正的數(shù)據(jù)的地方。關(guān)于UnwrapNestedRefs這個(gè)類(lèi)型,我后續(xù)單獨(dú)解釋 value: UnwrapNestedRefs<T>}// 判斷是否是Ref數(shù)據(jù)的方法// 對(duì)于is關(guān)鍵詞,若不熟悉,見(jiàn):http://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicatesexport function isRef(v: any): v is Ref { return v ? v[refSymbol] === true : false}// 見(jiàn)下文解釋export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
要想了解UnwrapNestedRefs
與UnwrapRef
,必須先要了解ts中的infer
。如果之前不了解,請(qǐng)先閱讀相關(guān)文檔。看完文檔,再建議去google一些案例看看加深下印象。infer
概念,也了解了它的日常用法。再來(lái)看源碼:// 不應(yīng)該繼續(xù)遞歸的引用數(shù)據(jù)類(lèi)型type BailTypes = | Function | Map<any, any> | Set<any> | WeakMap<any, any> | WeakSet<any>// 遞歸地獲取嵌套數(shù)據(jù)的類(lèi)型// Recursively unwraps nested value bindings.export type UnwrapRef<T> = { // 如果是ref類(lèi)型,繼續(xù)解套 ref: T extends Ref<infer V> ? UnwrapRef<V> : T // 如果是數(shù)組,循環(huán)解套 array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T // 如果是對(duì)象,遍歷解套 object: { [K in keyof T]: UnwrapRef<T[K]> } // 否則,停止解套 stop: T}[T extends Ref ? 'ref' : T extends Array<any> ? 'array' : T extends BailTypes ? 'stop' // bail out on types that shouldn't be unwrapped : T extends object ? 'object' : 'stop']// 聲明類(lèi)型別名:UnwrapNestedRefs// 它是這樣的類(lèi)型:如果該類(lèi)型已經(jīng)繼承于Ref,則不需要解套,否則可能是嵌套的ref,走遞歸解套export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
如果還是懵,建議后續(xù)再去看看infer的相關(guān)介紹。在這我們直接拋結(jié)果:Ref
是這樣的一種數(shù)據(jù)結(jié)構(gòu):它有個(gè)key為Symbol
的屬性做類(lèi)型標(biāo)識(shí),有個(gè)屬性value
用來(lái)存儲(chǔ)數(shù)據(jù)。這個(gè)數(shù)據(jù)可以是任意的類(lèi)型,唯獨(dú)不能是被嵌套了Ref
類(lèi)型的類(lèi)型。 具體來(lái)說(shuō)就是不能是這樣 Array<Ref>
或者這樣 { [key]: Ref }
。但很奇怪的是,這樣Ref<Ref>
又是可以的。具體為什么也不知道,所以我勇敢地提了個(gè)PR...Ref<Ref>
是不夠完美的,2019.10.10晚,我這PR被合并了。大家遇到疑問(wèn)時(shí),也可以勇敢的提PR,說(shuō)不定就被合了....)Ref
數(shù)據(jù)的value也有可能是Map<Ref>
這樣的數(shù)據(jù)類(lèi)型。Ref
類(lèi)型的數(shù)據(jù),是一種響應(yīng)式的數(shù)據(jù)。然后我們看其具體實(shí)現(xiàn):// 從@vue/shared中引入,判斷一個(gè)數(shù)據(jù)是否為對(duì)象// Record<any, any>代表了任意類(lèi)型key,任意類(lèi)型value的類(lèi)型// 為什么不是 val is object 呢?可以看下這個(gè)回答:https://stackoverflow.com/questions/52245366/in-typescript-is-there-a-difference-between-types-object-and-recordany-anyexport const isObject = (val: any): val is Record<any, any> => val !== null && typeof val === 'object'// 如果傳遞的值是個(gè)對(duì)象(包含數(shù)組/Map/Set/WeakMap/WeakSet),則使用reactive執(zhí)行,否則返回原數(shù)據(jù)// 從上篇文章知道,這個(gè)reactive就是將我們的數(shù)據(jù)轉(zhuǎn)成響應(yīng)式數(shù)據(jù)const convert = (val: any): any => (isObject(val) ? reactive(val) : val)export function ref<T>(raw: T): Ref<T> { // 轉(zhuǎn)化數(shù)據(jù) raw = convert(raw) const v = { [refSymbol]: true, get value() { // track的代碼在effect中,暫時(shí)不看,能猜到此處就是監(jiān)聽(tīng)函數(shù)收集依賴(lài)的方法。 track(v, OperationTypes.GET, '') // 返回剛剛被轉(zhuǎn)化后的數(shù)據(jù) return raw }, set value(newVal) { // 將設(shè)置的值,轉(zhuǎn)化為響應(yīng)式數(shù)據(jù),賦值給raw raw = convert(newVal) // trigger也暫時(shí)不看,能猜到此處就是觸發(fā)監(jiān)聽(tīng)函數(shù)執(zhí)行的方法 trigger(v, OperationTypes.SET, '') } } return v as Ref<T>}
其實(shí)最難理解的就在于這個(gè)ref
函數(shù)。我們看到,這里也定義了get/set,卻沒(méi)有任何Proxy
相關(guān)的操作。在之前的信息中我們知道reactive
能構(gòu)建出響應(yīng)式數(shù)據(jù),但要求傳參必須是對(duì)象。但ref
的入?yún)⑹菍?duì)象時(shí),同樣也需要reactive
做轉(zhuǎn)化。那ref
這個(gè)函數(shù)的目的到底是什么呢?為什么需要有它?However, the problem with going reactive-only is that the consumer of a composition function must keep the reference to the returned object at all times in order to retain reactivity. The object cannot be destructured or spread:對(duì)于基本數(shù)據(jù)類(lèi)型,函數(shù)傳遞或者對(duì)象解構(gòu)時(shí),會(huì)丟失原始數(shù)據(jù)的引用,換言之,我們沒(méi)法讓基本數(shù)據(jù)類(lèi)型,或者解構(gòu)后的變量(如果它的值也是基本數(shù)據(jù)類(lèi)型的話),成為響應(yīng)式的數(shù)據(jù)。
// 我們是永遠(yuǎn)沒(méi)辦法讓`a`或`x`這樣的基本數(shù)據(jù)成為響應(yīng)式的數(shù)據(jù)的,Proxy也無(wú)法劫持基本數(shù)據(jù)。const a = 1;const { x: 1 } = { x: 1 }
但是有時(shí)候,我們確實(shí)就是想一個(gè)數(shù)字、一個(gè)字符串是響應(yīng)式的,或者就是想利用解構(gòu)的寫(xiě)法。那怎么辦呢?只能通過(guò)創(chuàng)建一個(gè)對(duì)象,也即是源碼中的Ref
數(shù)據(jù),然后將原始數(shù)據(jù)保存在Ref
的屬性value
當(dāng)中,再將它的引用返回給使用者。既然是我們自己創(chuàng)造出來(lái)的對(duì)象,也就沒(méi)必要使用Proxy
再做代理了,直接劫持這個(gè)value
的get/set即可,這就是ref
函數(shù)與Ref
類(lèi)型的由來(lái)。ref
還沒(méi)法解決對(duì)象解構(gòu)的問(wèn)題,它只是將基本數(shù)據(jù)保持在一個(gè)對(duì)象的value
中,以實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式。對(duì)于對(duì)象的解構(gòu)還需要另外一個(gè)函數(shù):toRefs
。export function toRefs<T extends object>( object: T): { [K in keyof T]: Ref<T[K]> } { const ret: any = {} // 遍歷對(duì)象的所有key,將其值轉(zhuǎn)化為Ref數(shù)據(jù) for (const key in object) { ret[key] = toProxyRef(object, key) } return ret}function toProxyRef<T extends object, K extends keyof T>( object: T, key: K): Ref<T[K]> { const v = { [refSymbol]: true, get value() { // 注意,這里沒(méi)用到track return object[key] }, set value(newVal) { // 注意,這里沒(méi)用到trigger object[key] = newVal } } return v as Ref<T[K]>}
通過(guò)遍歷對(duì)象,將每個(gè)屬性值都轉(zhuǎn)成Ref
數(shù)據(jù),這樣解構(gòu)出來(lái)的還是Ref
數(shù)據(jù),自然就保持了響應(yīng)式數(shù)據(jù)的引用。但是源碼中有一點(diǎn)要注意,toRefs
函數(shù)中引用的是toProxyRef
而不是ref
,它并不會(huì)在get/set中注入track
跟trigger
,也就是說(shuō),向toRefs
傳入一個(gè)正常的對(duì)象,是不會(huì)返回一個(gè)響應(yīng)式的數(shù)據(jù)的。必須要傳遞一個(gè)已經(jīng)被reactive
執(zhí)行返回的對(duì)象才能有響應(yīng)式的效果。感覺(jué)這點(diǎn)可以?xún)?yōu)化,暫時(shí)也不知道小右這樣做的原因是什么。由于這里會(huì)牽扯到track
跟trigger
,而這兩個(gè)在我寫(xiě)本文時(shí)還沒(méi)研究,就沒(méi)膽子提PR了。ref
的源碼給看完了。reactive
,它是核心,從它開(kāi)始,內(nèi)部的各個(gè)api
開(kāi)始真正的串連。關(guān)鍵詞:響應(yīng),系統(tǒng)
客戶(hù)&案例
營(yíng)銷(xiāo)資訊
關(guān)于我們
客戶(hù)&案例
營(yíng)銷(xiāo)資訊
關(guān)于我們
微信公眾號(hào)
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。