vue?3?effect作用與原理解析
Vue 3 的 Effect(副作用) 是整個(gè)響應(yīng)式系統(tǒng)的核心機(jī)制,負(fù)責(zé)管理依賴追蹤和響應(yīng)式觸發(fā)。理解其作用和原理對(duì)掌握 Vue 的底層機(jī)制至關(guān)重要。
一、核心作用
1. 依賴追蹤(Dependency Tracking)
- 自動(dòng)跟蹤響應(yīng)式數(shù)據(jù)在副作用函數(shù)中的使用。
示例代碼:
import { reactive, effect } from 'vue' const obj = reactive({ count: 0 }) effect(() => { console.log(`count is: ${obj.count}`) })
- 當(dāng)首次執(zhí)行
effect
時(shí),函數(shù)() => console.log(...)
會(huì)被運(yùn)行。 - 觸發(fā)
obj.count
的get
操作,觸發(fā)依賴收集(將當(dāng)前effect
關(guān)聯(lián)到obj.count
)。
2. 自動(dòng)響應(yīng)(Automatic Re-run)
當(dāng)響應(yīng)式數(shù)據(jù)的依賴變化時(shí),自動(dòng)重新執(zhí)行副作用函數(shù):
obj.count++ // 觸發(fā)依賴更新,控制臺(tái)打印 "count is: 1"
3. 支撐高級(jí) API
computed
、watch
、組件渲染函數(shù)等底層都依賴于effect
實(shí)現(xiàn)。
二、實(shí)現(xiàn)原理
1. 核心類:ReactiveEffect
Vue 3 用 ReactiveEffect
類封裝副作用邏輯,簡(jiǎn)化后的源碼結(jié)構(gòu)如下:
class ReactiveEffect<T = any> { // 當(dāng)前 effect 的所有依賴項(xiàng)(其他響應(yīng)式對(duì)象) deps: Dep[] = [] // 構(gòu)造函數(shù)參數(shù) constructor( public fn: () => T, // 副作用函數(shù) public scheduler?: () => void // 調(diào)度函數(shù)(控制重新執(zhí)行方式) ) {} // 運(yùn)行副作用(觸發(fā)依賴收集) run() { activeEffect = this // 標(biāo)記當(dāng)前正在運(yùn)行的 effect try { return this.fn() } finally { activeEffect = undefined } } // 停止偵聽(tīng) stop() { /* 從所有依賴中移除自身 */ } }
2. 依賴收集流程(Track)
數(shù)據(jù)結(jié)構(gòu):
type Dep = Set<ReactiveEffect> // 依賴集合 type TargetMap = WeakMap<Object, Map<string, Dep>> // 全局依賴存儲(chǔ)
- 觸發(fā)時(shí)機(jī):響應(yīng)式數(shù)據(jù)的
get
操作觸發(fā)時(shí)。 - 流程:
根據(jù)響應(yīng)式對(duì)象 (
target
) 和鍵 (key
) 找到存入targetMap
的依賴集合 (dep
)。將當(dāng)前活躍的
activeEffect
添加到dep
中。同時(shí)將
dep
加入activeEffect.deps
(反向記錄,用于 cleanup)。
3. 觸發(fā)更新(Trigger)
- 觸發(fā)時(shí)機(jī):響應(yīng)式數(shù)據(jù)的
set
操作時(shí)。 - 流程:
根據(jù)
target
和key
從targetMap
獲取對(duì)應(yīng)的dep
集合。遍歷
dep
中所有effect
:- 如果有
scheduler
(如computed
),執(zhí)行調(diào)度器(優(yōu)化性能)。 - 否則直接執(zhí)行
effect.run()
。
- 如果有
4. 調(diào)度器(Scheduler)
允許控制 effect
如何重新執(zhí)行:
effect(() => { console.log(obj.count) }, { scheduler(effect) { // 如將 effect 推入微任務(wù)隊(duì)列中異步執(zhí)行 queueMicrotask(effect.run) } })
- 應(yīng)用場(chǎng)景:
watch
的異步批處理更新。computed
的值懶更新。
三、關(guān)鍵優(yōu)化設(shè)計(jì)
1. 嵌套 Effect 棧
用棧結(jié)構(gòu) effectStack
跟蹤嵌套的 effect:
function run() { if (!effectStack.includes(this)) { try { effectStack.push((activeEffect = this)) return this.fn() } finally { effectStack.pop() activeEffect = effectStack[effectStack.length - 1] } } }
- 解決問(wèn)題:組件嵌套時(shí)的依賴關(guān)系混亂。
2. Cleanup 機(jī)制
每次 effect 執(zhí)行前清理舊依賴:
function run() { cleanup(this) // 清理之前收集的舊依賴 // ...然后重新收集新依賴 }
- 解決問(wèn)題:動(dòng)態(tài)分支邏輯導(dǎo)致的無(wú)效依賴(如
v-if
切換導(dǎo)致的條件依賴)。
3. Lazy 執(zhí)行
可配置不立即執(zhí)行 effect:
const runner = effect(fn, { lazy: true }) runner() // 手動(dòng)執(zhí)行
- 應(yīng)用場(chǎng)景:
computed
屬性初始化時(shí)延遲計(jì)算。
四、與 Vue 各組件的關(guān)聯(lián)
1. 組件渲染
組件 render
函數(shù)被包裹在 effect
中:
function setupRenderEffect(instance) { effect(() => { const subTree = instance.render.call(instance.proxy) patch(instance.subTree, subTree) instance.subTree = subTree }, { scheduler: queueJob }) // 異步更新隊(duì)列 }
2. Computed 實(shí)現(xiàn)
computed
通過(guò) effect
+ 調(diào)度器實(shí)現(xiàn)懶更新:
const computedRef = new ComputedRefImpl( getter, () => { // 調(diào)度器 if (!this._dirty) { this._dirty = true trigger(this, 'set', 'value') } } )
3. Watch API
watch
基于 effect
的調(diào)度器實(shí)現(xiàn)異步回調(diào):
function watch(source, cb, { flush } = {}) { let scheduler if (flush === 'sync') { scheduler = cb } else { // 'post' 或其他默認(rèn)情況 scheduler = () => queuePostFlushCb(cb) } effect(() => traverse(source), { scheduler }) }
五、與 Vue 2 的對(duì)比
特性 | Vue 2 (Watcher) | Vue 3 (Effect) |
---|---|---|
依賴追蹤 | 通過(guò)遍歷數(shù)據(jù)觸發(fā) getter | 通過(guò) Proxy/Reflect 自動(dòng)追蹤 |
更新粒度 | 依賴組件級(jí)檢查 | 基于精確依賴的靶向更新 |
性能優(yōu)化 | 需手寫(xiě) pureComputed 等 | 內(nèi)置自動(dòng)的依賴清理和調(diào)度機(jī)制 |
內(nèi)存管理 | 易產(chǎn)生內(nèi)存泄漏(舊 Dep 引用問(wèn)題) | 通過(guò) WeakMap 自動(dòng)釋放無(wú)用依賴 |
六、源碼流程圖解
+---------------------+ | Reactive Object | +----------+----------+ │ 訪問(wèn)屬性時(shí) ▼ +---------------------+ | 觸發(fā) get 代理 +----→ track(target, key) +---------------------+ │ ▲ ▼ 存儲(chǔ)依賴關(guān)系 │ +---------------------+ +----------+ targetMap | | (WeakMap結(jié)構(gòu)) | +---------+-----------+ │ ▼ +---------------------+ | depsMap (Map) | | (key → Dep Set) | +---------+-----------+ │ ▼ +---------------------+ | dep (Set) | | (存儲(chǔ)所有關(guān)聯(lián)的 effect)| +---------------------+
總結(jié)
Vue 3 的 effect
通過(guò)以下機(jī)制成為響應(yīng)式系統(tǒng)的核心:
- Proxy 依賴收集:精確追蹤響應(yīng)式數(shù)據(jù)的使用。
- 調(diào)度器控制:提供靈活的回調(diào)執(zhí)行方式。
- 內(nèi)存安全:通過(guò)
WeakMap
自動(dòng)管理依賴。 - 框架級(jí)優(yōu)化:支持組件渲染、計(jì)算屬性、watch 等核心功能。
到此這篇關(guān)于vue 3 effect作用與原理的文章就介紹到這了,更多相關(guān)vue 3 effect作用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談關(guān)于iview表單驗(yàn)證的問(wèn)題
這篇文章主要介紹了淺談關(guān)于iview表單驗(yàn)證的問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09vue中后端做Excel導(dǎo)出功能返回?cái)?shù)據(jù)流前端的處理操作
這篇文章主要介紹了vue中后端做Excel導(dǎo)出功能返回?cái)?shù)據(jù)流前端的處理操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09element-ui 表格實(shí)現(xiàn)單元格可編輯的示例
下面小編就為大家分享一篇element-ui 表格實(shí)現(xiàn)單元格可編輯的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02vue data有值,但是頁(yè)面{{}} 取不到值的解決
這篇文章主要介紹了vue data有值,但是頁(yè)面{{}} 取不到值的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11Vue前端書(shū)寫(xiě)規(guī)范大全(非常詳細(xì)!)
在團(tuán)體開(kāi)發(fā)項(xiàng)目中,為了團(tuán)隊(duì)所有成員書(shū)寫(xiě)可維護(hù)的代碼,而不是一次性的代碼,讓團(tuán)隊(duì)當(dāng)中其他人看你的代碼能一目了然,下面這篇文章主要給大家介紹了關(guān)于Vue前端書(shū)寫(xiě)規(guī)范的相關(guān)資料,需要的朋友可以參考下2023-01-01vue中el-table樹(shù)狀表格末行合計(jì)實(shí)現(xiàn)
本文主要介紹了vue中el-table樹(shù)狀表格末行合計(jì)實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11