一文帶你了解vue3.0響應(yīng)式
我們知道Vue 2.0是利用Ojbect.defineProperty對對象的已有屬性值的讀取和修改進行劫持,但是這個API不能監(jiān)聽對象屬性的新增和刪除,此外為了深度劫持對象的內(nèi)部屬性,必須在初始化的時候?qū)?nèi)部屬性進行遞歸調(diào)用Ojbect.defineProperty,這就造成了一個性能上的消耗。為了解決這些問題,Vue 3.0利用Proxy重寫了響應(yīng)式邏輯并且優(yōu)化了相關(guān)性能。
使用案例
我們先來個示例看下Vue 3.0的響應(yīng)式API的寫法:

changePerson能改變響應(yīng)式數(shù)據(jù)person的值,person值的變化會觸發(fā)組件重新渲染而更新DOM。
這里我們可以看到Vue 3.0的使用中,開發(fā)者利用reactive函數(shù)自己去確定哪些數(shù)據(jù)為響應(yīng)式數(shù)據(jù),這樣就可以避免一些不必要的響應(yīng)式的性能消耗。例如案例中我們就不需要讓nowIndex成為響應(yīng)式數(shù)據(jù)。(當然Vue 2.0也可以在data函數(shù)外定義數(shù)據(jù),這樣也是非響應(yīng)式數(shù)據(jù))
我們接下來看看reactive函數(shù)的實現(xiàn)原理!
reactive API相關(guān)的流程
reactive

代碼說明:
- 1.如果目標對象
target是readonly對象,直接返回目標對象,因為readonly對象不能設(shè)置成響應(yīng)式對象 - 2.調(diào)用
createReactiveObject函數(shù)繼續(xù)流程。
createReactiveObject 創(chuàng)建響應(yīng)式對象

代碼說明:
- 1.如果目標對象不是數(shù)據(jù)或者對象,則直接返回對象,在開發(fā)環(huán)境給出錯誤警告提示。
- 2.如果
target已經(jīng)是一個Proxy對象,則直接返回target, (target['__v_raw']設(shè)計非常巧妙:如果target是Proxy對象,target['__v_raw']觸發(fā)get方法,在緩存對象reactiveMap中查找是否target對象的Proxy對象是否等于target自身)。這里處理了一個例外,如果是給響應(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不能是不可擴展對象,即target沒有被執(zhí)行過preventExtensions,seal和freeze這些方法;
3.target為Object或者Array;
4.target為Map,Set,WeakMap,WeakSet;
- 5.通過使用
Proxy函數(shù)劫持target對象,返回的結(jié)果即為響應(yīng)式對象了。這里的處理函數(shù)會根據(jù)target對象不同而不同(這兩個函數(shù)都是參數(shù)傳入的):
1.Object或者Array的處理函數(shù)是collectionHandlers;
2.Map,Set,WeakMap,WeakSet的處理函數(shù)是baseHandlers;
- 6.將響應(yīng)式對象存入
reactiveMap中緩存起來,key是target,value是proxy。
mutableHandlers 處理函數(shù)

我們知道訪問對象屬性會觸發(fā)get函數(shù),設(shè)置對象屬性會觸發(fā)set函數(shù),刪除對象屬性會觸發(fā)deleteProperty函數(shù),in操作符會觸發(fā)has函數(shù),getOwnPropertyNames會觸發(fā)ownKeys函數(shù)。我們接下來看看你這幾個函數(shù)的代碼邏輯。
get函數(shù)
由于沒有傳參,isReadonly和shallow都是默認參數(shù)false。

代碼邏輯:
- 1.如果獲取
__v_isReactive屬性,返回true, 表示target已經(jīng)是一個響應(yīng)式對象了; - 2.獲取
__v_isReadonly屬性,返回false;(readonly是響應(yīng)式的另外一個API,暫不解釋) - 3.獲取
__v_raw屬性,返回target本身,這個屬性用來判斷target是否已經(jīng)是響應(yīng)式對象; - 4.如果target是數(shù)組,且命中了一些屬性,例如includes, indexOf, lastIndexOf等,則執(zhí)行的是數(shù)組的這些函數(shù)方法,并對數(shù)組的每個元素執(zhí)行收集依賴track(
arr,TrackOpTypes.GET,i +''),然后通過Reflect獲取數(shù)組函數(shù)的值; - 5.Reflect求值;
- 6.判斷是否是特殊的屬性值:
symbol,__proto__,__v_isRef,__isVue, 如果是直接返回前面得到的res,不做后續(xù)處理; - 7.執(zhí)行收集依賴;
- 8.如果是ref, 如果target不是數(shù)組或者key不是整數(shù),就執(zhí)行數(shù)據(jù)拆包,這里涉及到另外一個響應(yīng)式APIref, 暫不解釋;
- 9.如果res是對象,遞歸執(zhí)行reactive,把res變成響應(yīng)式對象。這里是一個優(yōu)化小技巧,只有屬性值被訪問后才會被被劫持,避免了初始化就全劫持的性能消耗。
get函數(shù)的的調(diào)用時機
回答這個問題前我們需要回到前面一篇關(guān)于setup的文章—揭開Vue3.0 setup函數(shù)的神秘面紗。
- 在
setupStatefulComponent函數(shù)中會執(zhí)行setup()函數(shù),并得到執(zhí)行結(jié)果:

handleSetupResult處理結(jié)果的邏輯是間隔setupResult賦值給instance.setupState:

- 這個
instance.setupState被instance.ctx代理,所以訪問和修改instance.ctx就能直接訪問和修改instance.setupState:

- 我們以前提到過渲染生成子樹
VNode就是調(diào)用render函數(shù),我們用模板編譯看看我們例子中的render函數(shù)長啥樣子?

- 很清晰了,當渲染模板的時候,會從
ctx中取person屬性對象,其實就是取setupState的person屬性對象。當取setupState的person屬性對象的name,age,address時都會觸發(fā)get函數(shù)的調(diào)用,獲取對應(yīng)的值。
總結(jié):組件實例對象執(zhí)行render函數(shù)生成子樹VNode時,會調(diào)用響應(yīng)式對象的get函數(shù)。
track 收集依賴
我們上面的get函數(shù)的代碼解釋中兩次提到了收集依賴,那什么是收集依賴呢?
要實現(xiàn)響應(yīng)式,就是當數(shù)據(jù)變化后會自動實現(xiàn)一些功能,比如執(zhí)行某些函數(shù)等。因為副作用渲染函數(shù)能觸發(fā)組件的重新渲染而更新DOM,所以這里收集的依賴就是當數(shù)據(jù)變化后需要執(zhí)行的副作用渲染函數(shù)。
也就是說,當執(zhí)行get函數(shù)時就會收集對應(yīng)組件的副作用渲染函數(shù)。


我們可以拿我們的例子說明最后的結(jié)果:

set函數(shù)

代碼邏輯:
- 1.如果值沒有變化,直接返回;
- 2.通過
Reflect設(shè)置新值; - 3.不是原型鏈上的屬性,如果是新增屬性執(zhí)行
add類型的trigger,如果是修改屬性執(zhí)行set類型的trigger。(如果Reflect.set原型鏈上的屬性會再次調(diào)用setter,所以不用兩次執(zhí)行trigger)。
trigger 分發(fā)依賴

trigger代碼邏輯很清晰,就是從get函數(shù)中收集來的依賴targetMap中找到對應(yīng)的函數(shù),然后執(zhí)行這些副作用渲染函數(shù),更新DOM。
get和副作用渲染函數(shù)關(guān)聯(lián)
我們回過頭來再解答一個疑問:就是從get函數(shù)中收集來的副作用渲染函數(shù)是怎么確定的,即訪問person.name時如何確定關(guān)聯(lián)哪個副作用渲染函數(shù)呢?
我們接下來一步步梳理其中的邏輯:
- 組件掛載
mountComponent最后一步是執(zhí)行帶副作用的渲染函數(shù):

setupRenderEffect先定義了一個componentUpdateFn組件渲染函數(shù),然后將這個componentUpdateFn封裝在了ReactiveEffect中,并將ReactiveEffect對象的run方法賦值給組件對象的update屬性,然后執(zhí)行update方法,其實就是執(zhí)行ReactiveEffect對象的run方法。

ReactiveEffect的run方法持有了傳入的函數(shù),當前場景為componentUpdateFn組件渲染函數(shù),并且利用了兩個全局的變量effectStack和activeEffect。- 在執(zhí)行run方法時先將
componentUpdateFn賦值給activeEffect,并且壓入effectStack棧中,然后執(zhí)行componentUpdateFn方法。當執(zhí)行完成后componentUpdateFn出棧,并且賦值activeEffect為新的棧頂?shù)暮瘮?shù)。

componentUpdateFn執(zhí)行的時候會調(diào)用renderComponentRoot,本質(zhì)是執(zhí)行組件實例對象的render方法。

- 目前為止就到了本文的內(nèi)容了,render方法中如果訪問相應(yīng)式數(shù)據(jù)就會觸發(fā)get函數(shù),get中收集的就是

這里設(shè)計一個棧的結(jié)構(gòu),主要是為了解決effect嵌套的問題。
副作用渲染函數(shù)的執(zhí)行過濾
如果仔細思考下可能會有一個疑問?name,age,address都修改了,然后他們都關(guān)聯(lián)了同一個渲染函數(shù),理論上同時修改這三個值會觸發(fā)三次組件重新渲染呢,這明顯是不合理的。那Vue是如何控制只執(zhí)行一次呢?
- 我們需要再次回到
ReactiveEffect封裝componentUpdateFn渲染函數(shù)的地方,我們先看一眼第二個參數(shù)scheduler:

- 派發(fā)依賴的時候如果有
scheduler則會執(zhí)行scheduler:

queueJob的執(zhí)行邏輯是如果任務(wù)在隊列中就過濾掉不執(zhí)行。

結(jié)尾
本文詳細介紹了Vue3.0的相應(yīng)式原理:利用Proxy劫持對象,訪問對象的時候會觸發(fā)get方法,此時會進行依賴的收集;當修改對象數(shù)據(jù)的時候會觸發(fā)set方法,此時會派發(fā)依賴,即調(diào)用組件的副作用渲染函數(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)式對象如何實現(xiàn)方法的不同點
- 淺析vue3響應(yīng)式數(shù)據(jù)與watch屬性
- vue3中defineProps傳值使用ref響應(yīng)式失效詳解
- vue3.0響應(yīng)式函數(shù)原理詳細
- vue3.x源碼剖析之數(shù)據(jù)響應(yīng)式的深入講解
- 詳解Vue3的響應(yīng)式原理解析
- setup+ref+reactive實現(xiàn)vue3響應(yīng)式功能
- 手寫?Vue3?響應(yīng)式系統(tǒng)(核心就一個數(shù)據(jù)結(jié)構(gòu))
相關(guān)文章
vue中使用element ui的彈窗與echarts之間的問題詳解
這篇文章主要介紹了vue中使用element ui的彈窗與echarts之間的問題詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
詳解vue-video-player使用心得(兼容m3u8)
這篇文章主要介紹了詳解vue-video-player使用心得(兼容m3u8),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
三分鐘讓你快速學(xué)會axios在vue項目中的基本用法(推薦!)
Axios是一個基于Promise用于瀏覽器和nodejs的HTTP客戶端,下面這篇文章主要給大家介紹了如何通過三分鐘讓你快速學(xué)會axios在vue項目中的基本用法,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-04-04
詳解element-ui 組件el-autocomplete使用踩坑記錄
最近使用了el-autocomplete組件,本文主要介紹了element-ui 組件el-autocomplete使用踩坑記錄,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
解決vue cli4升級sass-loader(v8)后報錯問題
這篇文章主要介紹了解決vue cli4升級sass-loader(v8)后報錯問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07

