阿里云主机折上折
  • 微信号
Current Site:Index > Method to manually stop the response

Method to manually stop the response

Author:Chuan Chen 阅读数:27802人阅读 分类: Vue.js

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:

  1. The stop method of the runner returned by effect
  2. The global stop function
  3. The stop function returned by watch/watchEffect
  4. The effect property of computed properties returned by computed
// Global stop function declaration
declare function stop<T>(effect: ReactiveEffect<T>): void

Edge Cases in Stopping Reactivity

Considerations when handling edge cases:

  1. Calling stop on an already stopped effect will not throw an error
  2. After stopping, the runner can still be executed manually
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.