国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢(xún) 在線咨詢(xún)
15158846557 在線咨詢(xún)
所在位置: 首頁(yè) > 營(yíng)銷(xiāo)資訊 > 網(wǎng)站運(yùn)營(yíng) > Vue3響應(yīng)式系統(tǒng)源碼解析-Ref篇

Vue3響應(yīng)式系統(tǒng)源碼解析-Ref篇

時(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í)間的。

在這過(guò)程中,有些知識(shí)點(diǎn),跟庫(kù)本身可能沒(méi)什么關(guān)系,但如果不懂,又難繼續(xù)理解。對(duì)于這些知識(shí)點(diǎn),我會(huì)盡量少的解釋?zhuān)珪?huì)貼上盡量完善的文檔,方便不了解的同學(xué)先閱讀學(xué)習(xí)。

鑒于篇幅太長(zhǎng),信息量較大,我會(huì)將文章拆開(kāi),邊寫(xiě)邊發(fā),有興趣的同學(xué)可以連載閱讀,寫(xiě)完以后再匯總一篇,方便時(shí)間充沛的同學(xué)一股腦看。

前言

在上篇文章中說(shuō)道,ref是最影響源碼閱讀的文件。但如果不先搞明白它,看其他的只會(huì)更暈。我先幫大家理清ref的邏輯跟概念。

由于現(xiàn)在(2019/10/9)vue@3還未正式發(fā)版,大家還不熟悉其相關(guān)的用法。上篇文章雖然介紹了不少,但其實(shí)還是有不少疑問(wèn)。在閱讀本篇文章之前,如果有時(shí)間,建議先閱讀Vue官方對(duì)Composition API的介紹: 1. Vue Composition API 2. Ref Vs Reactive

讀完關(guān)于Composition API的介紹,會(huì)對(duì)了解本庫(kù)有更多認(rèn)識(shí),便于更好的理解源碼。

refreactive是整個(gè)源碼中的核心,通過(guò)這兩個(gè)方法創(chuàng)建了響應(yīng)式數(shù)據(jù)。要想完全吃透reactivity,必須先吃透這兩個(gè)。

Ref

ref最重要的作用,其實(shí)是提供了一套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>要想了解UnwrapNestedRefsUnwrapRef,必須先要了解ts中的infer。如果之前不了解,請(qǐng)先閱讀相關(guān)文檔。看完文檔,再建議去google一些案例看看加深下印象。

現(xiàn)在我們假設(shè)你了解了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ō)不定就被合了....)

另外,Map、Set、WeakMap、WeakSet也是不支持解套的。說(shuō)明Ref數(shù)據(jù)的value也有可能是Map<Ref>這樣的數(shù)據(jù)類(lèi)型。

說(shuō)回Ref,從上篇文章中,我們已經(jīng)了解到,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ù)的目的到底是什么呢?為什么需要有它?

在文章開(kāi)頭,我貼了這份官方介紹Ref vs Reactive,這其中其實(shí)已經(jīng)說(shuō)的很明白。

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)。

不過(guò)單靠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中注入tracktrigger,也就是說(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ì)牽扯到tracktrigger,而這兩個(gè)在我寫(xiě)本文時(shí)還沒(méi)研究,就沒(méi)膽子提PR了。

到這,我們就把ref的源碼給看完了。

下一章節(jié)我們開(kāi)始看reactive,它是核心,從它開(kāi)始,內(nèi)部的各個(gè)api開(kāi)始真正的串連。

關(guān)鍵詞:響應(yīng),系統(tǒng)

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉