Method to manually stop the response
Methods to Manually Stop Reactivity
Vue3's reactivity system is based on Proxy, with a highly automated dependency collection and update triggering process. However, in certain scenarios, manual control over reactivity is needed, such as for performance optimization or special logic handling.
Basic Principles of Stopping Reactivity
Each reactive object internally maintains a dependency map (targetMap
). Side effects created via the effect
function are collected into this map. Manually stopping reactivity essentially involves clearing these dependencies:
const state = reactive({ count: 0 })
// Create an effect
const stopHandle = effect(() => {
console.log(state.count)
})
// Stop reactivity
stopHandle.effect.stop()
Stopping via the Effect Return Value
The effect
function returns a Runner
object, which contains an effect
property with a stop
method:
const runner = effect(() => {
// Side effect logic
})
// Stop all reactivity for this effect
runner.effect.stop()
After stopping, the side effect no longer responds to dependency changes:
const obj = reactive({ a: 1 })
let dummy
const runner = effect(() => {
dummy = obj.a
})
obj.a = 2 // Triggers an update
runner.effect.stop()
obj.a = 3 // No longer triggers an update
console.log(dummy) // Still outputs 2
Custom Stop Logic
You can add an onStop
callback to the effect to implement custom cleanup logic:
const runner = effect(() => {
/* ... */
}, {
onStop() {
console.log('Effect stopped')
// Perform resource cleanup, etc.
}
})
Component-Level Reactivity Stopping
Use the stop
function in the setup
function to stop reactivity for the entire component:
import { reactive, stop } from 'vue'
export default {
setup() {
const state = reactive({ /* ... */ })
onUnmounted(() => {
stop(state) // Stop reactivity when the component unmounts
})
return { state }
}
}
Deep Stopping Reactivity
For nested objects, recursive stopping is required:
function deepStop(reactiveObj) {
if (!reactiveObj || !reactiveObj.__v_reactive) return
stop(reactiveObj)
for (const key in reactiveObj) {
const val = reactiveObj[key]
if (val && val.__v_reactive) {
deepStop(val)
}
}
}
const nestedObj = reactive({
a: 1,
b: { c: 2 }
})
deepStop(nestedObj) // Stop reactivity for the entire nested structure
Reactivity Markers and Stopping
Vue3 internally uses the __v_skip
marker to skip reactivity processing:
const obj = {}
Object.defineProperty(obj, '__v_skip', {
get() { return true }
})
const proxy = reactive(obj) // Will not create a reactive proxy
Combining with watch
Manually stopping a watch
listener:
const stopHandle = watch(
() => state.count,
(newVal) => {
console.log('count changed:', newVal)
}
)
// Stop the listener
stopHandle()
Internal Implementation of the Reactivity System
At the source code level, stopping reactivity primarily involves the implementation of the ReactiveEffect
class:
class ReactiveEffect {
active = true
deps: Dep[] = []
constructor(
public fn: () => T,
public scheduler?: () => void,
public onStop?: () => void
) {}
stop() {
if (this.active) {
cleanupEffect(this)
this.onStop?.()
this.active = false
}
}
}
function cleanupEffect(effect: ReactiveEffect) {
effect.deps.forEach(dep => {
dep.delete(effect)
})
effect.deps.length = 0
}
Performance Optimization Scenarios
For large list rendering, you can temporarily stop reactivity for non-visible areas:
const list = reactive([/* large dataset */])
let activeRange = [0, 50]
effect(() => {
// Only respond to changes in the visible range
const visibleData = list.slice(...activeRange)
renderList(visibleData)
})
// Update the visible range on scroll
function handleScroll() {
const newRange = calcVisibleRange()
if (newRange[0] !== activeRange[0] || newRange[1] !== activeRange[1]) {
// First stop the old effect
runner.effect.stop()
// Update the range and recreate the effect
activeRange = newRange
runner = effect(/* new effect */)
}
}
Special Cases with toRefs
Refs converted via toRefs
need to be stopped individually:
const state = reactive({ x: 1, y: 2 })
const refs = toRefs(state)
// Stop the original reactive object
stop(state)
// Refs remain reactive
refs.x.value = 3 // Still works
// Need to stop each ref individually
Object.values(refs).forEach(ref => {
if (ref.effect) ref.effect.stop()
})
Reactivity System Recovery Mechanism
Stopped reactive objects can be restored by recreating effects:
const obj = reactive({ data: null })
let runner
function startObserving() {
runner = effect(() => {
console.log('Data changed:', obj.data)
})
}
function stopObserving() {
runner.effect.stop()
}
// Can repeatedly stop and start
startObserving()
obj.data = 1 // Logs output
stopObserving()
obj.data = 2 // No output
startObserving()
obj.data = 3 // Logs output again
Stop-Related APIs in the Source Code
Vue3 exposes the following complete stop APIs:
- The
stop
method of the runner returned byeffect
- The global
stop
function - The stop function returned by
watch
/watchEffect
- The
effect
property of computed properties returned bycomputed
// Global stop function declaration
declare function stop<T>(effect: ReactiveEffect<T>): void
Edge Cases in Stopping Reactivity
Considerations when handling edge cases:
- Calling
stop
on an already stopped effect will not throw an error - After stopping, the runner can still be executed manually
- Stopping does not clear existing values
const obj = reactive({ val: 'initial' })
const runner = effect(() => obj.val)
runner.effect.stop()
obj.val = 'changed' // Does not trigger the effect
// Runner can still be executed manually
runner() // Outputs 'changed'
// Repeated stopping has no side effects
runner.effect.stop()
runner.effect.stop()
Reactivity Stopping and Memory Management
Long-running applications need to be mindful of memory leaks:
const globalState = reactive({ /* ... */ })
// Incorrect approach: Uncleaned effects will continue to occupy memory
effect(() => {
localStorage.setItem('cache', JSON.stringify(globalState))
})
// Correct approach: Provide a cleanup interface
let cacheEffect
function enableCache() {
cacheEffect = effect(/* ... */)
}
function disableCache() {
cacheEffect?.effect.stop()
}
Special Handling for SSR
Special attention is required for server-side rendering:
let serverSideEffect
if (!import.meta.env.SSR) {
// Execute only on the client side
serverSideEffect = effect(() => {
// Browser-specific logic
})
} else {
// Simulate stopped state on the server
const mockEffect = { stop: () => {} }
serverSideEffect = () => mockEffect
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:响应式系统的性能优化手段
下一篇:响应式系统边界情况处理