普通视图

发现新文章,点击刷新页面。
昨天 — 2025年12月7日首页

Vue3-watchEffect

作者 YaeZed
2025年12月6日 21:39

watchEffect 是 Vue 3 组合式 API 提供的另一个侦听器。它和 watch 目标一致(在数据变化时执行副作用),但使用方式更简洁,因为它会自动追踪依赖

1.核心理念

watchEffect 的核心理念是: “立即执行一次,并自动追踪函数内部用到的所有响应式数据,在它们变化时重新执行。”

它只需要一个参数:一个函数。

<template>
  <p>User ID: {{ userId }}</p>
  <button @click="changeUser">切换用户</button>
</template>

<script setup lang="ts">
import { ref, watchEffect } from 'vue';

const userId = ref(1);

// 1. 立即执行:
// watchEffect 会在 <script setup> 执行到这里时立即运行一次
// 它会自动“发现”内部用到了 userId.value
watchEffect(() => {
  // 当 watchEffect 运行时,它会追踪所有被“读取”的 .value
  console.log(`(watchEffect) 正在获取 ID: ${userId.value} 的数据...`);
  
  // 假设的 API 调用
  // fetchUserData(userId.value);
});

// 2. 自动追踪变化:
// 当 userId 变化时,上面的函数会 *自动* 重新运行
const changeUser = () => {
  userId.value++;
};
</script>

2.watchEffectwatch 的核心区别

特性 watch watchEffect
依赖源 手动指定 (必须明确告诉它侦听谁) 自动追踪 (它会侦听函数体内部用到的所有数据)
立即执行 默认不会 (需配置 { immediate: true }) 默认立即执行
访问旧值 可以 (回调参数 (newVal, oldVal)) 不可以 (回调不接收任何参数)
侧重点 适合精确控制 更“轻量级”,适合简单的、自动化的副作用

3.watchEffect 的高级用法

  • 停止侦听:watchEffect 同样会返回一个“停止句柄” (Stop Handle) 函数。

<script setup> 中,它也会自动绑定到组件生命周期,并在组件卸载时自动停止,所以你通常不需要手动停止。

import { watchEffect } from 'vue';

const stopHandle = watchEffect(() => {
  // ...
});

// 在未来的某个时刻
stopHandle(); // 停止这个 effect
  • 清除副作用 (onInvalidate):watchEffect 的回调函数可以接收一个 onInvalidate 函数作为参数。这个函数用于注册一个“失效”时的回调。

watchEffect 即将重新运行时(或在侦听器被停止时),onInvalidate 注册的回调会先执行。这在处理异步操作竞态时非常有用(例如,短时间内多次触发 API 请求)。

举个栗子

<template>
  <div>
    <h3>onInvalidate</h3>
    <p>在控制台中查看日志,并快速输入:</p>
    <input v-model="query" />
    <p>当前查询: {{ query }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref, watchEffect } from 'vue';

const query = ref('vue');

watchEffect((onInvalidate) => {
  // 1. 创建 AbortController
  const controller = new AbortController();
  const { signal } = controller;

  console.log(`(Fetch) 正在搜索: ${query.value}`);
  
  // 2. 使用 Open Library 的 API
  // 对 URL 进行了编码 (encodeURIComponent) 以处理特殊字符
  const url = `https://openlibrary.org/search.json?q=${encodeURIComponent(query.value)}`;

  fetch(url, { signal })
    .then(response => response.json()) // 将响应解析为 JSON
    .then(data => {
      // 3. 成功获取数据
      console.log(`(Success) 成功获取: ${query.value}`, data.docs.length, '条结果');
    })
    .catch(err => {
      // 4. 捕获错误
      if (err.name === 'AbortError') {
        //  预期的中止错误
        console.log(`(Abort) 已取消上一次请求: ${query.value}`);
      } else {
        // 其他网络错误
        console.error('Fetch 错误:', err);
      }
    });

  // 5. 注册“失效”回调
  onInvalidate(() => {
    console.log('...查询变化太快,Effect 即将失效,中止上一个请求...');
    // 6. 中止上一次的 fetch 请求
    controller.abort();
  });
});

</script>

当快速输入333时,控制台打印

image.png

检查网络,前两次的快速请求都被取消了,只有最后一次成功

image.png

4.总结

onInvalidate 适用于任何有“清理”需求的副作用

  1. 取消异步请求:(最常用) fetch, axios 等。
  2. 清除定时器:清除 setTimeoutsetInterval
  3. 解绑事件监听器:如果在 watchEffect 内部用 window.addEventListener 动态添加了一个事件,你可以在 onInvalidate 中用 window.removeEventListener 将其移除,防止内存泄漏。

两个关键区别:

  • 防抖/节流 (Debounce/Throttle) (用 watch):

    • 目标:减少执行次数
    • 逻辑:“等一等再执行”。
  • 清理副作用 (Cleanup) (用 watchEffect + onInvalidate):

    • 目标:防止旧操作干扰新操作
    • 逻辑:“开始新的之前,先确保旧的已经停止了”。
❌
❌