vue3中使用watchEffect和watch函数时应当防止内存泄漏

154 阅读6分钟

官方文档:cn.vuejs.org/api/reactiv…

也就是说当使用他们两个时候,使用完成之后要及时停止他们,防止一直在运行,停止他们之后,也可以再次开启。

watchEffect()

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

类型

function watchEffect(
  effect: (onCleanup: OnCleanup) => void,
  options?: WatchEffectOptions
): WatchHandle

type OnCleanup = (cleanupFn: () => void) => void

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

interface WatchHandle {
  (): void // 可调用,与 `stop` 相同
  pause: () => void
  resume: () => void
  stop: () => void
}

 

详细信息

第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。

第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。

默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。详见回调的触发时机。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

返回值是一个用来停止该副作用的函数。

示例

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 输出 0

count.value++
// -> 输出 1

副作用清除:

watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel)
  data.value = await response
})

停止侦听器:

const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

暂停/恢复侦听器:

const { stop, pause, resume } = watchEffect(() => {})

// 暂停侦听器
pause()

// 稍后恢复
resume()

// 停止
stop()

副作用清理:

watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(newId)
  // 如果 `id` 变化,则调用 `cancel`,
  // 如果之前的请求未完成,则取消该请求
  onCleanup(cancel)
  data.value = await response
})

3.5+ 中的副作用清理:

import { onWatcherCleanup } from 'vue'

watchEffect(async () => {
  const { response, cancel } = doAsyncWork(newId)
  // 如果 `id` 变化,则调用 `cancel`,
  // 如果之前的请求未完成,则取消该请求
  onWatcherCleanup(cancel)
  data.value = await response
})

选项:

watchEffect(() => {}, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})
  • 参考

    <ul>
    	<li><a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="指南 - 侦听器" href="https://cn.vuejs.org/guide/essentials/watchers.html#watcheffect" title="指南 - 侦听器">指南 - 侦听器</a></li>
    	<li><a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="指南 - 侦听器调试" href="https://cn.vuejs.org/guide/extras/reactivity-in-depth.html#watcher-debugging" title="指南 - 侦听器调试">指南 - 侦听器调试</a></li>
    </ul>
    </li>
    

watchPostEffect()

watchEffect() 使用 flush: 'post' 选项时的别名。

watchSyncEffect()

watchEffect() 使用 flush: 'sync' 选项时的别名。

watch()

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

类型

// 侦听单个来源
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): WatchHandle

// 侦听多个来源
function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): WatchHandle

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

type WatchSource<T> =
  | Ref<T> // ref
  | (() => T) // getter
  | T extends object
  ? T
  : never // 响应式对象

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认:false
  deep?: boolean | number // 默认:false
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  once?: boolean // 默认:false (3.4+)
}

interface WatchHandle {
  (): void // 可调用,与 `stop` 相同
  pause: () => void
  resume: () => void
  stop: () => void
}

为了便于阅读,对类型进行了简化

  • 详细信息

    <p><code>watch()</code>&nbsp;默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。</p>
    
    <p>第一个参数是侦听器的<strong></strong>。这个来源可以是以下几种:</p>
    
    <p>第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。</p>
    
    <p>当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。</p>
    
    <p>第三个可选的参数是一个对象,支持以下这些选项:</p>
    
    <p>&nbsp;<a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="watchEffect()" href="https://cn.vuejs.org/api/reactivity-core.html#watcheffect" title="watchEffect()">watchEffect()</a>&nbsp;相比,<code>watch()</code>&nbsp;使我们可以:</p>
    
    <ul>
    	<li>一个函数,返回一个值</li>
    	<li>一个 ref</li>
    	<li>一个响应式对象</li>
    	<li>...或是由以上类型的值组成的数组</li>
    	<li><strong><code>immediate</code></strong>:在侦听器创建时立即触发回调。第一次调用时旧值是&nbsp;<code>undefined</code></li>
    	<li><strong><code>deep</code></strong>:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。在 3.5+ 中,此参数还可以是指示最大遍历深度的数字。参考<a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="深层侦听器" href="https://cn.vuejs.org/guide/essentials/watchers.html#deep-watchers" title="深层侦听器">深层侦听器</a></li>
    	<li><strong><code>flush</code></strong>:调整回调函数的刷新时机。参考<a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="回调的刷新时机" href="https://cn.vuejs.org/guide/essentials/watchers.html#callback-flush-timing" title="回调的刷新时机">回调的刷新时机</a>&nbsp;<a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="watchEffect()" href="https://cn.vuejs.org/api/reactivity-core.html#watcheffect" title="watchEffect()">watchEffect()</a></li>
    	<li><strong><code>onTrack / onTrigger</code></strong>:调试侦听器的依赖。参考<a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="调试侦听器" href="https://cn.vuejs.org/guide/extras/reactivity-in-depth.html#watcher-debugging" title="调试侦听器">调试侦听器</a></li>
    	<li><strong><code>once</code></strong>:(3.4+) 回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。</li>
    	<li>懒执行副作用;</li>
    	<li>更加明确是应该由哪个状态触发侦听器重新执行;</li>
    	<li>可以访问所侦听状态的前一个值和当前值。</li>
    </ul>
    </li>
    <li>
    <p><strong>示例</strong></p>
    
    <p>侦听一个 getter 函数:</p>
    js
    
    <pre>
    

    const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } )

    <p>侦听一个 ref:</p>
    js
    
    <pre>
    

    const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })

    <p>当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:</p>
    js
    
    <pre>
    

    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ })

    <p>当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用&nbsp;<code>{ deep: true }</code>&nbsp;强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。</p>
    js
    
    <pre>
    

    const state = reactive({ count: 0 }) watch( () => state, (newValue, oldValue) => { // newValue === oldValue }, { deep: true } )

    <p>当直接侦听一个响应式对象时,侦听器会自动启用深层模式:</p>
    js
    
    <pre>
    

    const state = reactive({ count: 0 }) watch(state, () => { /* 深层级变更状态所触发的回调 */ })

    <p><code>watch()</code>&nbsp;&nbsp;<a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="watchEffect()" href="https://cn.vuejs.org/api/reactivity-core.html#watcheffect" title="watchEffect()">watchEffect()</a>&nbsp;享有相同的刷新时机和调试选项:</p>
    js
    
    <pre>
    

    watch(source, callback, { flush: 'post', onTrack(e) { debugger }, onTrigger(e) { debugger } })

    <p>停止侦听器:</p>
    js
    
    <pre>
    

    const stop = watch(source, callback)

    // 当已不再需要该侦听器时: stop()

    <p>暂停/恢复侦听器:</p>
    js
    
    <pre>
    

    const { stop, pause, resume } = watchEffect(() => {})

    // 暂停侦听器 pause()

    // 稍后恢复 resume()

    // 停止 stop()

    <p>副作用清理:</p>
    js
    
    <pre>
    

    watch(id, async (newId, oldId, onCleanup) => { const { response, cancel } = doAsyncWork(newId) // 当 id 变化时,cancel 将被调用, // 取消之前的未完成的请求 onCleanup(cancel) data.value = await response })

    <p>3.5+ 中的副作用清理:</p>
    js
    
    <pre>
    

    import { onWatcherCleanup } from 'vue'

    watch(id, async (newId) => { const { response, cancel } = doAsyncWork(newId) onWatcherCleanup(cancel) data.value = await response })

  • 参考

    <ul>
    	<li><a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="指南 - 侦听器" href="https://cn.vuejs.org/guide/essentials/watchers.html" title="指南 - 侦听器">指南 - 侦听器</a></li>
    	<li><a data-link-icon="https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83A" data-link-title="指南 - 侦听器调试" href="https://cn.vuejs.org/guide/extras/reactivity-in-depth.html#watcher-debugging" title="指南 - 侦听器调试">指南 - 侦听器调试</a></li>
    </ul>
    </li>
    

onWatcherCleanup() 

注册一个清理函数,在当前侦听器即将重新运行时执行。只能在 watchEffect 作用函数或 watch 回调函数的同步执行期间调用 (即不能在异步函数的 await 语句之后调用)。

  • 类型

    ts
    <pre>
    

    function onWatcherCleanup( cleanupFn: () => void, failSilently?: boolean ): void

  • 示例

    ts

    <pre>
    

    import { watch, onWatcherCleanup } from 'vue'

    watch(id, (newId) => { const { response, cancel } = doAsyncWork(newId) // 如果 id 变化,则调用 cancel, // 如果之前的请求未完成,则取消该请求 onWatcherCleanup(cancel) })