一文帶你了解vue3.0響應(yīng)式
我們知道Vue 2.0
是利用Ojbect.defineProperty
對(duì)對(duì)象的已有屬性值的讀取和修改進(jìn)行劫持,但是這個(gè)API
不能監(jiān)聽(tīng)對(duì)象屬性的新增和刪除,此外為了深度劫持對(duì)象的內(nèi)部屬性,必須在初始化的時(shí)候?qū)?nèi)部屬性進(jìn)行遞歸調(diào)用Ojbect.defineProperty
,這就造成了一個(gè)性能上的消耗。為了解決這些問(wèn)題,Vue 3.0
利用Proxy
重寫了響應(yīng)式邏輯并且優(yōu)化了相關(guān)性能。
使用案例
我們先來(lái)個(gè)示例看下Vue 3.0
的響應(yīng)式API的寫法:
changePerson
能改變響應(yīng)式數(shù)據(jù)person
的值,person
值的變化會(huì)觸發(fā)組件重新渲染而更新DOM。
這里我們可以看到Vue 3.0
的使用中,開(kāi)發(fā)者利用reactive
函數(shù)自己去確定哪些數(shù)據(jù)為響應(yīng)式數(shù)據(jù),這樣就可以避免一些不必要的響應(yīng)式的性能消耗。例如案例中我們就不需要讓nowIndex
成為響應(yīng)式數(shù)據(jù)。(當(dāng)然Vue 2.0
也可以在data
函數(shù)外定義數(shù)據(jù),這樣也是非響應(yīng)式數(shù)據(jù))
我們接下來(lái)看看reactive
函數(shù)的實(shí)現(xiàn)原理!
reactive API相關(guān)的流程
reactive
代碼說(shuō)明:
- 1.如果目標(biāo)對(duì)象
target
是readonly對(duì)象,直接返回目標(biāo)對(duì)象,因?yàn)閞eadonly對(duì)象不能設(shè)置成響應(yīng)式對(duì)象 - 2.調(diào)用
createReactiveObject
函數(shù)繼續(xù)流程。
createReactiveObject 創(chuàng)建響應(yīng)式對(duì)象
代碼說(shuō)明:
- 1.如果目標(biāo)對(duì)象不是數(shù)據(jù)或者對(duì)象,則直接返回對(duì)象,在開(kāi)發(fā)環(huán)境給出錯(cuò)誤警告提示。
- 2.如果
target
已經(jīng)是一個(gè)Proxy
對(duì)象,則直接返回target, (target['__v_raw']
設(shè)計(jì)非常巧妙:如果target
是Proxy
對(duì)象,target['__v_raw']
觸發(fā)get
方法,在緩存對(duì)象reactiveMap
中查找是否target
對(duì)象的Proxy
對(duì)象是否等于target
自身)。這里處理了一個(gè)例外,如果是給響應(yīng)式對(duì)象執(zhí)行readonly
函數(shù)則需要繼續(xù)。 - 3.在
reactiveMap
中查找是否已經(jīng)有了對(duì)應(yīng)的Proxy
對(duì)象,則直接返回對(duì)應(yīng)的Proxy
對(duì)象。 - 4.確保只有特定的數(shù)據(jù)能變成響應(yīng)式,否則直接返回
target
。響應(yīng)式白名單如下所示:
1.target
沒(méi)有被執(zhí)行過(guò)markRaw
方法,或者說(shuō)target
對(duì)象沒(méi)有__v_skip
屬性值或者__v_skip
屬性的值為false
;
2.target
不能是不可擴(kuò)展對(duì)象,即target
沒(méi)有被執(zhí)行過(guò)preventExtensions
,seal
和freeze
這些方法;
3.target
為Object
或者Array
;
4.target
為Map
,Set
,WeakMap
,WeakSet
;
- 5.通過(guò)使用
Proxy
函數(shù)劫持target
對(duì)象,返回的結(jié)果即為響應(yīng)式對(duì)象了。這里的處理函數(shù)會(huì)根據(jù)target
對(duì)象不同而不同(這兩個(gè)函數(shù)都是參數(shù)傳入的):
1.Object
或者Array
的處理函數(shù)是collectionHandlers
;
2.Map
,Set
,WeakMap
,WeakSet
的處理函數(shù)是baseHandlers
;
- 6.將響應(yīng)式對(duì)象存入
reactiveMap
中緩存起來(lái),key
是target
,value
是proxy
。
mutableHandlers 處理函數(shù)
我們知道訪問(wèn)對(duì)象屬性會(huì)觸發(fā)get
函數(shù),設(shè)置對(duì)象屬性會(huì)觸發(fā)set
函數(shù),刪除對(duì)象屬性會(huì)觸發(fā)deleteProperty
函數(shù),in
操作符會(huì)觸發(fā)has
函數(shù),getOwnPropertyNames
會(huì)觸發(fā)ownKeys
函數(shù)。我們接下來(lái)看看你這幾個(gè)函數(shù)的代碼邏輯。
get函數(shù)
由于沒(méi)有傳參,isReadonly
和shallow
都是默認(rèn)參數(shù)false。
代碼邏輯:
- 1.如果獲取
__v_isReactive
屬性,返回true, 表示target已經(jīng)是一個(gè)響應(yīng)式對(duì)象了; - 2.獲取
__v_isReadonly
屬性,返回false;(readonly是響應(yīng)式的另外一個(gè)API,暫不解釋) - 3.獲取
__v_raw
屬性,返回target本身,這個(gè)屬性用來(lái)判斷target是否已經(jīng)是響應(yīng)式對(duì)象; - 4.如果target是數(shù)組,且命中了一些屬性,例如includes, indexOf, lastIndexOf等,則執(zhí)行的是數(shù)組的這些函數(shù)方法,并對(duì)數(shù)組的每個(gè)元素執(zhí)行收集依賴track(
arr
,TrackOpTypes.GET
,i +
''),然后通過(guò)Reflect獲取數(shù)組函數(shù)的值; - 5.Reflect求值;
- 6.判斷是否是特殊的屬性值:
symbol
,__proto__
,__v_isRef
,__isVu
e, 如果是直接返回前面得到的res,不做后續(xù)處理; - 7.執(zhí)行收集依賴;
- 8.如果是ref, 如果target不是數(shù)組或者key不是整數(shù),就執(zhí)行數(shù)據(jù)拆包,這里涉及到另外一個(gè)響應(yīng)式APIref, 暫不解釋;
- 9.如果res是對(duì)象,遞歸執(zhí)行reactive,把res變成響應(yīng)式對(duì)象。這里是一個(gè)優(yōu)化小技巧,只有屬性值被訪問(wèn)后才會(huì)被被劫持,避免了初始化就全劫持的性能消耗。
get函數(shù)的的調(diào)用時(shí)機(jī)
回答這個(gè)問(wèn)題前我們需要回到前面一篇關(guān)于setup的文章—揭開(kāi)Vue3.0 setup函數(shù)的神秘面紗。
- 在
setupStatefulComponent
函數(shù)中會(huì)執(zhí)行setup()函數(shù),并得到執(zhí)行結(jié)果:
handleSetupResult
處理結(jié)果的邏輯是間隔setupResult
賦值給instance.setupState
:
- 這個(gè)
instance.setupState
被instance.ctx
代理,所以訪問(wèn)和修改instance.ctx
就能直接訪問(wèn)和修改instance.setupState
:
- 我們以前提到過(guò)渲染生成子樹(shù)
VNode
就是調(diào)用render
函數(shù),我們用模板編譯看看我們例子中的render
函數(shù)長(zhǎng)啥樣子?
- 很清晰了,當(dāng)渲染模板的時(shí)候,會(huì)從
ctx
中取person
屬性對(duì)象,其實(shí)就是取setupState
的person
屬性對(duì)象。當(dāng)取setupState
的person
屬性對(duì)象的name
,age
,address
時(shí)都會(huì)觸發(fā)get
函數(shù)的調(diào)用,獲取對(duì)應(yīng)的值。
總結(jié):組件實(shí)例對(duì)象執(zhí)行render函數(shù)生成子樹(shù)VNode時(shí),會(huì)調(diào)用響應(yīng)式對(duì)象的get函數(shù)。
track 收集依賴
我們上面的get
函數(shù)的代碼解釋中兩次提到了收集依賴,那什么是收集依賴呢?
要實(shí)現(xiàn)響應(yīng)式,就是當(dāng)數(shù)據(jù)變化后會(huì)自動(dòng)實(shí)現(xiàn)一些功能,比如執(zhí)行某些函數(shù)等。因?yàn)?strong>副作用渲染函數(shù)能觸發(fā)組件的重新渲染而更新DOM,所以這里收集的依賴就是當(dāng)數(shù)據(jù)變化后需要執(zhí)行的副作用渲染函數(shù)。
也就是說(shuō),當(dāng)執(zhí)行get
函數(shù)時(shí)就會(huì)收集對(duì)應(yīng)組件的副作用渲染函數(shù)。
我們可以拿我們的例子說(shuō)明最后的結(jié)果:
set函數(shù)
代碼邏輯:
- 1.如果值沒(méi)有變化,直接返回;
- 2.通過(guò)
Reflect
設(shè)置新值; - 3.不是原型鏈上的屬性,如果是新增屬性執(zhí)行
add
類型的trigger
,如果是修改屬性執(zhí)行set
類型的trigger
。(如果Reflect.set原型鏈上的屬性會(huì)再次調(diào)用setter,所以不用兩次執(zhí)行trigger)。
trigger 分發(fā)依賴
trigger
代碼邏輯很清晰,就是從get函數(shù)中收集來(lái)的依賴targetMap中找到對(duì)應(yīng)的函數(shù),然后執(zhí)行這些副作用渲染函數(shù),更新DOM。
get和副作用渲染函數(shù)關(guān)聯(lián)
我們回過(guò)頭來(lái)再解答一個(gè)疑問(wèn):就是從get函數(shù)中收集來(lái)的副作用渲染函數(shù)是怎么確定的,即訪問(wèn)person.name時(shí)如何確定關(guān)聯(lián)哪個(gè)副作用渲染函數(shù)呢?
我們接下來(lái)一步步梳理其中的邏輯:
- 組件掛載
mountComponent
最后一步是執(zhí)行帶副作用的渲染函數(shù):
setupRenderEffect
先定義了一個(gè)componentUpdateFn
組件渲染函數(shù),然后將這個(gè)componentUpdateFn
封裝在了ReactiveEffect
中,并將ReactiveEffect
對(duì)象的run
方法賦值給組件對(duì)象的update
屬性,然后執(zhí)行update
方法,其實(shí)就是執(zhí)行ReactiveEffect
對(duì)象的run
方法。
ReactiveEffect
的run方法持有了傳入的函數(shù),當(dāng)前場(chǎng)景為componentUpdateFn
組件渲染函數(shù),并且利用了兩個(gè)全局的變量effectStack
和activeEffect
。- 在執(zhí)行run方法時(shí)先將
componentUpdateFn
賦值給activeEffect
,并且壓入effectStack
棧中,然后執(zhí)行componentUpdateFn
方法。當(dāng)執(zhí)行完成后componentUpdateFn
出棧,并且賦值activeEffect
為新的棧頂?shù)暮瘮?shù)。
componentUpdateFn
執(zhí)行的時(shí)候會(huì)調(diào)用renderComponentRoot
,本質(zhì)是執(zhí)行組件實(shí)例對(duì)象的render方法。
- 目前為止就到了本文的內(nèi)容了,render方法中如果訪問(wèn)相應(yīng)式數(shù)據(jù)就會(huì)觸發(fā)get函數(shù),get中收集的就是
這里設(shè)計(jì)一個(gè)棧的結(jié)構(gòu),主要是為了解決effect嵌套的問(wèn)題。
副作用渲染函數(shù)的執(zhí)行過(guò)濾
如果仔細(xì)思考下可能會(huì)有一個(gè)疑問(wèn)?name,age,address都修改了,然后他們都關(guān)聯(lián)了同一個(gè)渲染函數(shù),理論上同時(shí)修改這三個(gè)值會(huì)觸發(fā)三次組件重新渲染呢,這明顯是不合理的。那Vue是如何控制只執(zhí)行一次呢?
- 我們需要再次回到
ReactiveEffect
封裝componentUpdateFn
渲染函數(shù)的地方,我們先看一眼第二個(gè)參數(shù)scheduler
:
- 派發(fā)依賴的時(shí)候如果有
scheduler
則會(huì)執(zhí)行scheduler
:
queueJob
的執(zhí)行邏輯是如果任務(wù)在隊(duì)列中就過(guò)濾掉不執(zhí)行。
結(jié)尾
本文詳細(xì)介紹了Vue3.0的相應(yīng)式原理:利用Proxy劫持對(duì)象,訪問(wèn)對(duì)象的時(shí)候會(huì)觸發(fā)get方法,此時(shí)會(huì)進(jìn)行依賴的收集;當(dāng)修改對(duì)象數(shù)據(jù)的時(shí)候會(huì)觸發(fā)set方法,此時(shí)會(huì)派發(fā)依賴,即調(diào)用組件的副作用渲染函數(shù)(其實(shí)不限于), 這樣組件就能重新渲染,DOM更新。
到此這篇關(guān)于一文帶你了解vue3.0響應(yīng)式的文章就介紹到這了,更多相關(guān)vue3.0響應(yīng)式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue3.0 響應(yīng)式系統(tǒng)源碼逐行分析講解
- vue3 響應(yīng)式對(duì)象如何實(shí)現(xiàn)方法的不同點(diǎn)
- 淺析vue3響應(yīng)式數(shù)據(jù)與watch屬性
- vue3中defineProps傳值使用ref響應(yīng)式失效詳解
- vue3.0響應(yīng)式函數(shù)原理詳細(xì)
- vue3.x源碼剖析之?dāng)?shù)據(jù)響應(yīng)式的深入講解
- 詳解Vue3的響應(yīng)式原理解析
- setup+ref+reactive實(shí)現(xiàn)vue3響應(yīng)式功能
- 手寫?Vue3?響應(yīng)式系統(tǒng)(核心就一個(gè)數(shù)據(jù)結(jié)構(gòu))
相關(guān)文章
vue中使用element ui的彈窗與echarts之間的問(wèn)題詳解
這篇文章主要介紹了vue中使用element ui的彈窗與echarts之間的問(wèn)題詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10詳解vue-video-player使用心得(兼容m3u8)
這篇文章主要介紹了詳解vue-video-player使用心得(兼容m3u8),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08三分鐘讓你快速學(xué)會(huì)axios在vue項(xiàng)目中的基本用法(推薦!)
Axios是一個(gè)基于Promise用于瀏覽器和nodejs的HTTP客戶端,下面這篇文章主要給大家介紹了如何通過(guò)三分鐘讓你快速學(xué)會(huì)axios在vue項(xiàng)目中的基本用法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04詳解element-ui 組件el-autocomplete使用踩坑記錄
最近使用了el-autocomplete組件,本文主要介紹了element-ui 組件el-autocomplete使用踩坑記錄,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03解決vue cli4升級(jí)sass-loader(v8)后報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了解決vue cli4升級(jí)sass-loader(v8)后報(bào)錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07Vue+OpenLayer實(shí)現(xiàn)測(cè)距功能
OpenLayers?是一個(gè)專為Web?GIS?客戶端開(kāi)發(fā)提供的JavaScript?類庫(kù)包,用于實(shí)現(xiàn)標(biāo)準(zhǔn)格式發(fā)布的地圖數(shù)據(jù)訪問(wèn)。本文將通過(guò)Vue和OpenLayer實(shí)現(xiàn)測(cè)距功能?,需要的可以參考一下2022-04-04