在 Vue 3 应用中,如何利用 `watch` 和 `watchEffect`,处理复杂的响应式副作用,例如 API 请求和事件监听?

诸位,各位靓仔靓女,欢迎来到老夫的 Vue 3 响应式副作用处理讲座!今天咱们不搞那些虚头巴脑的概念,直接上干货,聊聊 watchwatchEffect 这哥俩,在处理复杂场景,比如 API 请求和事件监听时,到底该咋用,才能让你的代码优雅又高效。

首先,咱们先来简单回顾一下 watchwatchEffect 的基本概念,毕竟温故而知新嘛。

watch:精确监控,按需触发

watch 就像一个尽职尽责的保安,它会盯着你指定的特定目标(响应式数据),只有当这个目标发生变化时,它才会触发你设定的回调函数。这哥们儿非常精确,不会没事儿瞎触发,用起来也更加可控。

watchEffect:自动追踪,立即执行

watchEffect 则像一个好奇宝宝,它会自己去探索你的回调函数中用到了哪些响应式数据,然后自动建立依赖关系。只要这些依赖的响应式数据发生变化,它就会毫不犹豫地执行回调函数。而且,这哥们儿一上来就会立即执行一次,先睹为快。

好了,概念复习完毕,下面咱们就开始进入实战环节,看看这哥俩在 API 请求和事件监听这些复杂场景中,到底该怎么耍。

场景一:API 请求

API 请求在前端开发中简直是家常便饭,而响应式地处理 API 请求更是 Vue 应用的必备技能。咱们先来看一个简单的例子:根据用户输入的搜索关键词,动态地请求搜索结果。

1. 使用 watch 实现

首先,咱们定义一个响应式的搜索关键词 keyword,然后使用 watch 来监听它的变化,一旦 keyword 发生改变,就发起 API 请求。

<template>
  <input type="text" v-model="keyword" placeholder="请输入关键词">
  <ul>
    <li v-for="result in searchResults" :key="result.id">{{ result.title }}</li>
  </ul>
</template>

<script setup>
import { ref, watch } from 'vue';

const keyword = ref('');
const searchResults = ref([]);

watch(keyword, async (newKeyword, oldKeyword) => {
  // 防抖处理,避免频繁请求
  if (newKeyword === oldKeyword) return; // 初始值避免触发
  if (!newKeyword.trim()) {
    searchResults.value = []; //关键词为空时,清空结果
    return;
  }
  try {
    const response = await fetch(`/api/search?keyword=${newKeyword}`);
    const data = await response.json();
    searchResults.value = data.results;
  } catch (error) {
    console.error('搜索失败', error);
    // 错误处理,例如显示错误提示
  }
}, { immediate: false, deep: false }); // immediate: false 表示初始时不执行,deep: false 表示不深度监听对象内部属性
</script>

在这个例子中,我们使用 watch 监听 keyword 的变化,并在回调函数中发起 API 请求。为了避免用户频繁输入导致大量的请求,我们还加入了简单的防抖处理(当然,更专业的防抖可以使用 lodash 等库)。

注意几个点:

  • immediate: false:设置 immediatefalse,可以避免在组件初始化时就发起一次不必要的请求。
  • deep: false:通常情况下,我们不需要深度监听 keyword 内部的属性,所以设置 deepfalse 可以提高性能。

2. 使用 watchEffect 实现

接下来,咱们看看使用 watchEffect 该怎么实现同样的功能。

<template>
  <input type="text" v-model="keyword" placeholder="请输入关键词">
  <ul>
    <li v-for="result in searchResults" :key="result.id">{{ result.title }}</li>
  </ul>
</template>

<script setup>
import { ref, watchEffect } from 'vue';

const keyword = ref('');
const searchResults = ref([]);

watchEffect(async () => {
    if (!keyword.value.trim()) {
        searchResults.value = [];
        return;
    }

  try {
    const response = await fetch(`/api/search?keyword=${keyword.value}`);
    const data = await response.json();
    searchResults.value = data.results;
  } catch (error) {
    console.error('搜索失败', error);
  }
});
</script>

使用 watchEffect 的代码看起来更加简洁,因为它会自动追踪 keyword 的变化,并自动执行回调函数。

watch vs watchEffect:API 请求场景对比

特性 watch watchEffect
目标 明确指定要监听的响应式数据 自动追踪回调函数中使用的响应式数据
触发时机 只有目标发生变化时才触发 依赖的响应式数据发生变化时触发,且初始时立即执行一次
代码简洁度 相对繁琐,需要手动指定监听目标和回调函数参数 更加简洁,自动追踪依赖
可控性 更高,可以精确控制触发时机和回调函数参数 相对较低,依赖关系由 Vue 自动管理

总结:

  • 如果需要精确控制 API 请求的触发时机和参数,或者需要进行一些额外的逻辑处理(例如防抖、节流),那么 watch 是一个更好的选择。
  • 如果只需要简单地根据响应式数据的变化发起 API 请求,并且不需要太多的控制,那么 watchEffect 可以让你的代码更加简洁。

场景二:事件监听

除了 API 请求,事件监听也是前端开发中常见的需求。咱们来看一个例子:监听窗口的滚动事件,并根据滚动距离动态地改变导航栏的样式。

1. 使用 watch 实现

<template>
  <div class="navbar" :class="{ 'fixed': isFixed }">
    导航栏
  </div>
  <div class="content" style="height: 2000px;">
    滚动我
  </div>
</template>

<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';

const isFixed = ref(false);
const scrollY = ref(0);

const handleScroll = () => {
  scrollY.value = window.scrollY;
};

onMounted(() => {
  window.addEventListener('scroll', handleScroll);
});

onUnmounted(() => {
  window.removeEventListener('scroll', handleScroll);
});

watch(scrollY, (newScrollY) => {
  isFixed.value = newScrollY > 100;
});
</script>

<style scoped>
.navbar {
  background-color: #fff;
  padding: 10px;
  position: relative;
  transition: all 0.3s ease;
}

.navbar.fixed {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.content {
  margin-top: 20px;
}
</style>

在这个例子中,我们首先使用 addEventListener 监听窗口的滚动事件,并在 handleScroll 函数中更新 scrollY 的值。然后,我们使用 watch 监听 scrollY 的变化,并根据滚动距离动态地设置 isFixed 的值,从而改变导航栏的样式。

2. 使用 watchEffect 实现

<template>
  <div class="navbar" :class="{ 'fixed': isFixed }">
    导航栏
  </div>
  <div class="content" style="height: 2000px;">
    滚动我
  </div>
</template>

<script setup>
import { ref, watchEffect, onMounted, onUnmounted } from 'vue';

const isFixed = ref(false);
const scrollY = ref(0);

const handleScroll = () => {
  scrollY.value = window.scrollY;
};

onMounted(() => {
  window.addEventListener('scroll', handleScroll);
});

onUnmounted(() => {
  window.removeEventListener('scroll', handleScroll);
});

watchEffect(() => {
  isFixed.value = scrollY.value > 100;
});
</script>

<style scoped>
.navbar {
  background-color: #fff;
  padding: 10px;
  position: relative;
  transition: all 0.3s ease;
}

.navbar.fixed {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.content {
  margin-top: 20px;
}
</style>

同样地,使用 watchEffect 的代码更加简洁,因为它会自动追踪 scrollY 的变化,并自动执行回调函数。

watch vs watchEffect:事件监听场景对比

特性 watch watchEffect
目标 明确指定要监听的响应式数据 自动追踪回调函数中使用的响应式数据
触发时机 只有目标发生变化时才触发 依赖的响应式数据发生变化时触发,且初始时立即执行一次
代码简洁度 相对繁琐,需要手动指定监听目标和回调函数参数 更加简洁,自动追踪依赖
可控性 更高,可以精确控制触发时机和回调函数参数 相对较低,依赖关系由 Vue 自动管理

总结:

  • 在事件监听场景中,watchwatchEffect 的选择也取决于你的需求。如果需要对事件进行一些额外的处理,或者需要精确控制回调函数的执行时机,那么 watch 是一个更好的选择。
  • 如果只需要简单地根据事件触发的响应式数据来更新组件的状态,那么 watchEffect 可以让你的代码更加简洁。

进阶技巧:watchwatchEffect 的高级用法

除了基本的用法之外,watchwatchEffect 还有一些高级用法,可以帮助你更好地处理复杂的响应式副作用。

1. flush: 'post'

默认情况下,watchwatchEffect 的回调函数会在 Vue 组件更新之前执行。如果你需要在组件更新之后执行回调函数,可以使用 flush: 'post' 选项。

<script setup>
import { ref, watch } from 'vue';

const count = ref(0);

watch(count, (newCount) => {
  console.log('count changed', newCount);
}, { flush: 'post' });
</script>

2. onInvalidate

watchEffect 提供了一个 onInvalidate 函数,可以在副作用函数即将重新执行时调用。这可以用来清理之前的副作用,例如取消未完成的 API 请求或移除事件监听器。

<script setup>
import { ref, watchEffect } from 'vue';

const keyword = ref('');

watchEffect((onInvalidate) => {
  let timeoutId = setTimeout(() => {
    console.log('搜索', keyword.value);
  }, 500);

  onInvalidate(() => {
    clearTimeout(timeoutId);
    console.log('取消之前的搜索');
  });
});
</script>

在这个例子中,我们使用 onInvalidate 来清除之前的 setTimeout 定时器,从而实现防抖效果。

3. 停止 watchEffect

watchEffect 会返回一个停止函数,可以用来手动停止副作用的执行。

<script setup>
import { ref, watchEffect, onUnmounted } from 'vue';

const count = ref(0);

const stop = watchEffect(() => {
  console.log('count', count.value);
});

onUnmounted(() => {
  stop(); // 组件卸载时停止副作用
});
</script>

总结:选择合适的工具,事半功倍

watchwatchEffect 都是 Vue 3 中强大的响应式工具,可以帮助你处理各种复杂的副作用。选择哪个工具取决于你的具体需求。

场景 推荐工具
精确控制触发时机 watch,可以精确指定监听的目标和回调函数参数,进行更细粒度的控制。
简单依赖追踪 watchEffect,自动追踪依赖,代码更加简洁。
需要清理副作用 watchEffect,配合 onInvalidate 可以方便地清理之前的副作用,例如取消 API 请求或移除事件监听器。
需要停止副作用的执行 watchEffect,返回的停止函数可以手动停止副作用的执行,避免不必要的资源消耗。
API 请求 watch (需要精确控制,例如防抖、节流) 或 watchEffect (简单场景,只需要根据响应式数据变化发起请求)。
事件监听 watch (需要对事件进行额外的处理) 或 watchEffect (简单场景,只需要根据事件触发的响应式数据来更新组件状态)。

记住,没有银弹!根据你的实际情况,选择最合适的工具,才能让你的代码更加清晰、高效。

好了,今天的讲座就到这里。希望大家能够掌握 watchwatchEffect 的精髓,在 Vue 3 的开发中游刃有余! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注