通常我們是不會直接使用effect的,因為effect是一個底層的API,在我們使用Vue3的時候Vue預設會幫我們呼叫effect。 effect翻譯為作用,意思是使其發生作用,而這個使其的其就是我們傳入的函數,所以effect的作用就是讓我們傳入的函數發生作用,也就是執行這個函數。執行過程簡圖如下:
接下來先透過範例了解effect的基本用法,然後再去了解原理。
一、effect用法
1、基本用法
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }) obj.count++
結果會先列印1, 然後在obj.count
之後列印2。
流程簡圖如下:
執行#effect(fun)
// 先执行 fun() // 打印出1 const runner = new ReactiveEffect(fn) return runner runner: { run() { this.fun() //执行fun }, stop() { } }
console.log (obj.count)
track依賴收集結構如下:
#obj.count
觸發依賴,執行runner.run(), 實際運行的是
() => { console.log(obj.count) }
所以又印出2
2、lazy屬性為true
此值為true 時,只有在第一次手動呼叫runner 後,依賴數據變更時,才會自動執行effect 的回調,可以理解為effect 的是手動調用runner 後才首次執行
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }, { lazy: true }) runner() obj.count++
只會打印出2
原因是effect源碼中有如下邏輯:
3、options中包含onTrack
let events = [] const onTrack = (e) => { events.push(e) } const obj = reactive({ foo: 1, bar: 2 }) const runner = effect( () => { console.log(obj.foo) }, { onTrack } ) console.log('runner', runner) obj.foo++ console.log("events", events)
看下events的列印結果:
[ { effect: runner, // effect 函数的返回值 target: toRaw(obj), // 表示的是哪个响应式数据发生了变化 type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值 key: 'foo' } ]
二、原始碼分析
1、effect方法的實作
// packages/reactivity/src/effect.ts export interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean scheduler?: EffectScheduler scope?: EffectScope allowRecurse?: boolean onStop?: () => void } export function effect<T = any>( fn: () => T, // 副作用函数 options?: ReactiveEffectOptions // 结构如上 ): ReactiveEffectRunner { // 如果 fn 对象上有 effect 属性 if ((fn as ReactiveEffectRunner).effect) { // 那么就将 fn 替换为 fn.effect.fn fn = (fn as ReactiveEffectRunner).effect.fn } // 创建一个响应式副作用函数 const _effect = new ReactiveEffect(fn) if (options) { // 将配置项合并到响应式副作用函数上 extend(_effect, options) // 如果配置项中有 scope 属性(该属性的作用是指定副作用函数的作用域) if (options.scope) recordEffectScope(_effect, options.scope) } if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn() } // _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // 将响应式副作用函数赋值给 runner.effect runner.effect = _effect return runner }
核心程式碼:
建立一個響應式副作用函數const _effect = new ReactiveEffect( fn)
,其運行結果如下:
非lazy狀態執行響應式副作用函數_effect.run()
if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn() }
_effect.run
作用域綁定到_effect
// _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
傳回副作用函數runner
##2、ReactiveEffect函數原始碼export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = [] // 响应式依赖项的集合
parent: ReactiveEffect | undefined = undefined
/**
* Can be attached after creation
* @internal
*/
computed?: ComputedRefImpl<T>
/**
* @internal
*/
allowRecurse?: boolean
/**
* @internal
*/
private deferStop?: boolean
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
// 记录当前 ReactiveEffect 对象的作用域
recordEffectScope(this, scope)
}
run() {
// 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果
if (!this.active) {
return this.fn()
}
// 寻找当前 ReactiveEffect 对象的最顶层的父级作用域
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack // 是否要跟踪
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
// 记录父级作用域为当前活动的 ReactiveEffect 对象
this.parent = activeEffect
activeEffect = this // 将当前活动的 ReactiveEffect 对象设置为 “自己”
shouldTrack = true // 将 shouldTrack 设置为 true (表示是否需要收集依赖)
// effectTrackDepth 用于标识当前的 effect 调用栈的深度,执行一次 effect 就会将 effectTrackDepth 加 1
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
// 初始依赖追踪标记
initDepMarkers(this)
} else {
// 清除依赖追踪标记
cleanupEffect(this)
}
// 返回副作用函数执行结果
return this.fn()
} finally {
// 如果 effect调用栈的深度 没有超过阈值
if (effectTrackDepth <= maxMarkerBits) {
// 确定最终的依赖追踪标记
finalizeDepMarkers(this)
}
// 执行完毕会将 effectTrackDepth 减 1
trackOpBit = 1 << --effectTrackDepth
// 执行完毕,将当前活动的 ReactiveEffect 对象设置为 “父级作用域”
activeEffect = this.parent
// 将 shouldTrack 设置为上一个值
shouldTrack = lastShouldTrack
// 将父级作用域设置为 undefined
this.parent = undefined
// 延时停止,这个标志是在 stop 方法中设置的
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
// 如果当前 活动的 ReactiveEffect 对象是 “自己”
// 延迟停止,需要执行完当前的副作用函数之后再停止
if (activeEffect === this) {
// 在 run 方法中会判断 deferStop 的值,如果为 true,就会执行 stop 方法
this.deferStop = true
} else if (this.active) {// 如果当前 ReactiveEffect 对象处于活动状态
cleanupEffect(this) // 清除所有的依赖追踪标记
if (this.onStop) {
this.onStop()
}
this.active = false // 将 active 设置为 false
}
}
}
- run方法的作用就是執行副作用函數,在執行副作用函數的過程中,會收集依賴;
- stop方法的作用就是停止目前的ReactiveEffect對象,停止之後,就不會再收集依賴了;
- activeEffect和this並不是每次都相等的,因為activeEffect會跟著呼叫棧的深度而變化,而this則是固定的;
obj. count就會觸發依賴收集
const runner = effect(() => { console.log(obj.count) })觸發的入口在get攔截器裡面
function createGetter(isReadonly = false, shallow = false) { // 闭包返回 get 拦截器方法 return function get(target: Target, key: string | symbol, receiver: object) { // ... if (!isReadonly) { track(target, TrackOpTypes.GET, key) } // ... }2、track原始碼
const targetMap = new WeakMap();
/**
* 收集依赖
* @param target target 触发依赖的对象,例子中的obj
* @param type 操作类型 比如obj.count就是get
* @param key 指向对象的key, 比如obj.count就是count
*/
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) { // 是否应该依赖收集 & 当前的new ReactiveEffect()即指向的就是当前正在执行的副作用函数
// 如果 targetMap 中没有 target,就会创建一个 Map
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep())) // createDep 生成dep = { w:0, n: 0}
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
}
shouldTrack在上面也講過,它的作用是控制是否收集依賴;activeEffect就是我們剛剛講的ReactiveEffect對象,它指向的就是當前正在執行的副作用函數;track方法的作用就是收集依賴,它的實作非常簡單,就是在targetMap中記錄下target和key;targetMap是一個WeakMap,它的鍵是target,值是一個Map,這個Map的鍵是key,值是一個Set;targetMap的結構偽代碼如下:targetMap = { target: { key: dep }, // 比如: obj: { count: { w: 0, n: 0 } } }
##dev環境為增加響應式偵錯會增加
eventInfo<pre class='brush:php;toolbar:false;'>const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined</pre>
eventInfo結構如下:
#如果dep 中沒有當前的ReactiveEffect 對象,就會加進去, 作用就把對象的屬性操作與副作用函數建立關聯,接下來看
3、trackEffects(dep, eventInfo)原始碼解讀
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { // 执行之前 dep = Set(0) {w: 0, n: 0} // 执行之后 dep = Set(0) {w: 0, n: 2} dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. shouldTrack = !dep.has(activeEffect!) } if (shouldTrack) { // 将activeEffect添加到dep dep.add(activeEffect!) activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { // onTrack逻辑 activeEffect!.onTrack( extend( { effect: activeEffect! }, debuggerEventExtraInfo! ) ) } } }dep.add(activeEffect!)
如果dep 中沒有目前的ReactiveEffect 對象,就會加進去
function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { //... const result = Reflect.set(target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) // 触发ADD依赖更新 } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) //触发SET依赖更新 } } //... }
1、trigger依赖更新
// 路径:packages/reactivity/src/effect.ts export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { const depsMap = targetMap.get(target) // 获取depsMap, targetMap是在track中创建的依赖 if (!depsMap) { // never been tracked return } let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target deps = [...depsMap.values()] } else if (key === 'length' && isArray(target)) { const newLength = Number(newValue) depsMap.forEach((dep, key) => { if (key === 'length' || key >= newLength) { deps.push(dep) } }) } else { // schedule runs for SET | ADD | DELETE if (key !== void 0) { deps.push(depsMap.get(key)) } // also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break } } const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } } else { const effects: ReactiveEffect[] = [] for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } }
const depsMap = targetMap.get(target)
获取 targetMap 中的 depsMap targetMap结构如下:
执行以上语句之后的depsMap结构如下:
将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中deps.push(depsMap.get(key))
之后的deps结构如下:
triggerEffects(deps[0], eventInfo)
const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } }
trigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。
在这里的实现其实并没有执行,主要是收集一些需要执行的副作用函数,然后在丢给triggerEffects函数去执行,接下来看看triggerEffects函数。
2、triggerEffects(deps[0], eventInfo)
export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } }
主要步骤
const effects = isArray(dep) ? dep : [...dep]
获取effects
triggerEffect(effect, debuggerEventExtraInfo)
执行effect,接下来看看源码
3、triggerEffect(effect, debuggerEventExtraInfo)
function triggerEffect( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { // 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行 if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } // 如果 effect 是一个调度器,就会执行 scheduler if (effect.scheduler) { effect.scheduler() } else { // 其它情况执行 effect.run() effect.run() } } }
effect.run()就是执行副作用函数
以上是Vue3響應式核心之effect怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Vue.js與前端技術棧緊密集成,提升開發效率和用戶體驗。 1)構建工具:與Webpack、Rollup集成,實現模塊化開發。 2)狀態管理:與Vuex集成,管理複雜應用狀態。 3)路由:與VueRouter集成,實現單頁面應用路由。 4)CSS預處理器:支持Sass、Less,提升樣式開發效率。

Netflix選擇React來構建其用戶界面,因為React的組件化設計和虛擬DOM機制能夠高效處理複雜界面和頻繁更新。 1)組件化設計讓Netflix將界面分解成可管理的小組件,提高了開發效率和代碼可維護性。 2)虛擬DOM機制通過最小化DOM操作,確保了Netflix用戶界面的流暢性和高性能。

Vue.js被開發者喜愛因為它易於上手且功能強大。 1)其響應式數據綁定係統自動更新視圖。 2)組件系統提高了代碼的可重用性和可維護性。 3)計算屬性和偵聽器增強了代碼的可讀性和性能。 4)使用VueDevtools和檢查控制台錯誤是常見的調試技巧。 5)性能優化包括使用key屬性、計算屬性和keep-alive組件。 6)最佳實踐包括清晰的組件命名、使用單文件組件和合理使用生命週期鉤子。

Vue.js是一個漸進式的JavaScript框架,適用於構建高效、可維護的前端應用。其關鍵特性包括:1.響應式數據綁定,2.組件化開發,3.虛擬DOM。通過這些特性,Vue.js簡化了開發過程,提高了應用性能和可維護性,使其在現代Web開發中備受歡迎。

Vue.js和React各有優劣,選擇取決於項目需求和團隊情況。 1)Vue.js適合小型項目和初學者,因其簡潔和易上手;2)React適用於大型項目和復雜UI,因其豐富的生態系統和組件化設計。

Vue.js通過多種功能提升用戶體驗:1.響應式系統實現數據即時反饋;2.組件化開發提高代碼復用性;3.VueRouter提供平滑導航;4.動態數據綁定和過渡動畫增強交互效果;5.錯誤處理機制確保用戶反饋;6.性能優化和最佳實踐提升應用性能。

Vue.js在Web開發中的角色是作為一個漸進式JavaScript框架,簡化開發過程並提高效率。 1)它通過響應式數據綁定和組件化開發,使開發者能專注於業務邏輯。 2)Vue.js的工作原理依賴於響應式系統和虛擬DOM,優化性能。 3)實際項目中,使用Vuex管理全局狀態和優化數據響應性是常見實踐。

Vue.js是由尤雨溪在2014年發布的漸進式JavaScript框架,用於構建用戶界面。它的核心優勢包括:1.響應式數據綁定,數據變化自動更新視圖;2.組件化開發,UI可拆分為獨立、可複用的組件。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

Atom編輯器mac版下載
最受歡迎的的開源編輯器

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

禪工作室 13.0.1
強大的PHP整合開發環境

WebStorm Mac版
好用的JavaScript開發工具