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