阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 手动停止响应的方法

手动停止响应的方法

作者:陈川 阅读数:27801人阅读 分类: Vue.js

手动停止响应的方法

Vue3的响应式系统基于Proxy实现,依赖收集和触发更新的过程自动化程度高。但在某些场景下需要手动控制响应式行为,比如性能优化或特殊逻辑处理。

停止响应式的基本原理

每个响应式对象内部维护一个依赖映射表(targetMap),通过effect函数创建的副作用会被收集到该表中。手动停止响应本质上是清除这些依赖关系:

const state = reactive({ count: 0 })

// 创建effect
const stopHandle = effect(() => {
  console.log(state.count)
})

// 停止响应
stopHandle.effect.stop()

通过effect返回值停止

effect函数返回一个Runner对象,该对象的effect属性包含stop方法:

const runner = effect(() => {
  // 副作用逻辑
})

// 停止该effect的所有响应
runner.effect.stop()

停止后,该副作用不再响应依赖的变化:

const obj = reactive({ a: 1 })
let dummy
const runner = effect(() => {
  dummy = obj.a
})

obj.a = 2 // 触发更新
runner.effect.stop()
obj.a = 3 // 不再触发更新
console.log(dummy) // 仍输出2

自定义stop逻辑

可以在effect中添加onStop回调实现自定义清理逻辑:

const runner = effect(() => {
  /* ... */
}, {
  onStop() {
    console.log('effect已停止')
    // 执行资源释放等操作
  }
})

组件级别的停止响应

在setup函数中使用stop函数停止整个组件的响应式:

import { reactive, stop } from 'vue'

export default {
  setup() {
    const state = reactive({ /* ... */ })
    
    onUnmounted(() => {
      stop(state) // 组件卸载时停止响应
    })

    return { state }
  }
}

深度停止响应式

对于嵌套对象,需要递归停止响应:

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) // 停止整个嵌套结构的响应

响应式标记与停止

Vue3内部使用__v_skip标记跳过响应式处理:

const obj = {}
Object.defineProperty(obj, '__v_skip', {
  get() { return true }
})

const proxy = reactive(obj) // 不会创建响应式代理

与watch的结合使用

手动停止watch监听:

const stopHandle = watch(
  () => state.count,
  (newVal) => {
    console.log('count changed:', newVal)
  }
)

// 停止监听
stopHandle()

响应式系统的内部实现

在源码层面,停止响应主要涉及ReactiveEffect类的实现:

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
}

性能优化场景

在大型列表渲染时,可以临时停止非可见区域的响应:

const list = reactive([/* 大量数据 */])
let activeRange = [0, 50]

effect(() => {
  // 只响应可见区域数据变化
  const visibleData = list.slice(...activeRange)
  renderList(visibleData)
})

// 滚动时更新可见区域
function handleScroll() {
  const newRange = calcVisibleRange()
  if (newRange[0] !== activeRange[0] || newRange[1] !== activeRange[1]) {
    // 先停止旧effect
    runner.effect.stop()
    // 更新范围后重新创建effect
    activeRange = newRange
    runner = effect(/* 新effect */)
  }
}

与toRefs的特殊情况

toRefs转换后的ref需要单独停止:

const state = reactive({ x: 1, y: 2 })
const refs = toRefs(state)

// 停止原始响应式对象
stop(state)

// refs仍然保持响应
refs.x.value = 3 // 仍然有效

// 需要单独停止每个ref
Object.values(refs).forEach(ref => {
  if (ref.effect) ref.effect.stop()
})

响应式系统的恢复机制

已停止的响应式对象可以通过重新创建effect恢复:

const obj = reactive({ data: null })
let runner

function startObserving() {
  runner = effect(() => {
    console.log('Data changed:', obj.data)
  })
}

function stopObserving() {
  runner.effect.stop()
}

// 可以重复停止和启动
startObserving()
obj.data = 1 // 输出日志
stopObserving()
obj.data = 2 // 无输出
startObserving()
obj.data = 3 // 再次输出日志

源码中的stop相关API

Vue3暴露的完整停止API包括:

  1. effect返回的runner的stop方法
  2. 全局stop函数
  3. watch/watchEffect返回的停止函数
  4. computed返回的计算属性的effect属性
// 全局stop函数声明
declare function stop<T>(effect: ReactiveEffect<T>): void

响应式停止的边界情况

处理边缘案例时的注意事项:

  1. 已停止的effect再次调用stop不会报错
  2. 停止后runner仍然可以手动执行
  3. 停止操作不会清除已有值
const obj = reactive({ val: 'initial' })
const runner = effect(() => obj.val)

runner.effect.stop()
obj.val = 'changed' // 不触发effect

// runner仍可手动执行
runner() // 输出'changed'

// 重复停止无副作用
runner.effect.stop()
runner.effect.stop()

响应式停止与内存管理

长时间运行的应用需要注意内存泄漏问题:

const globalState = reactive({ /* ... */ })

// 错误的做法:未清理的effect会持续占用内存
effect(() => {
  localStorage.setItem('cache', JSON.stringify(globalState))
})

// 正确的做法:提供清理接口
let cacheEffect
function enableCache() {
  cacheEffect = effect(/* ... */)
}

function disableCache() {
  cacheEffect?.effect.stop()
}

与SSR的特殊处理

服务端渲染时需要特别注意响应式停止:

let serverSideEffect

if (!import.meta.env.SSR) {
  // 仅在客户端执行
  serverSideEffect = effect(() => {
    // 浏览器特定逻辑
  })
} else {
  // 服务端模拟停止状态
  const mockEffect = { stop: () => {} }
  serverSideEffect = () => mockEffect
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌