前言 📝

在前端开发中,防抖(debounce)是一种常见的技术,用于限制函数执行的频率。特别是在处理用户输入、窗口大小调整等频繁触发的事件时,防抖可以显著提高性能。本文将探讨在Vue3中实现防抖的几种方法,并分析它们的优缺点。

1. 什么是防抖?

防抖是一种技术,它确保一个函数在一定时间内只执行一次,即使它被多次调用。如果在等待时间内再次调用该函数,计时器会重置。

典型应用场景:

  • 搜索框输入建议
  • 窗口大小调整事件
  • 滚动事件处理

2. Vue中的防抖实现方案

2.1 使用工具函数实现防抖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function debounce<T extends (...args: any[]) => any>(
fn: T,
delay = 300
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout> | null = null;

return function(...args: Parameters<T>) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn(...args);
}, delay);
};
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup lang="ts">
import { ref } from "vue";
import debounce from './debounce.ts'

const message = ref('');

const handleChange = (e) => {
  console.log('执行')
  message.vlaue = e.target.value
}
const debounceFn = debounce(handleChange, 3000)
</script>

<template>
<input @input="debounceFn" />
  <p style="color: #fff">{{message}}</p>
</template>

优点

  • 通用性强,可以在任何地方使用
  • 不依赖Vue特性,纯JavaScript实现

缺点

  • 需要手动处理事件对象
  • 在模板中使用时不够直观

2.2 响应式Ref防抖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { customRef, onUnmounted } from "vue";

export default function useDebounceRef<T>(value: T, delay = 200) {
  let timer: ReturnType<typeof setTimeout> | null = null;

    // 组件卸载时自动清除
    onUnmounted(() => {
      if (timer) clearTimeout(timer)
    })

  return customRef((track, trigger) => ({
    get() {
      track();
      return value;
    },
    set(newValue: T) {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
        value = newValue;
        trigger();
      }, delay);
    },
  }));
}

使用:

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">
import { ref } from "vue";
import useDebounceRef from './useDebounceRef.ts'

const message = useDebounceRef('', 3000);
</script>

<template>
  <input v-model="message" />
  <p style="color: #fff">{{message}}</p>
</template>

优点

  • 无缝支持 v-model:可以直接在模板中使用 v-model 绑定,开发者体验极佳
  • 自动依赖追踪:通过 track()trigger() 方法完美融入 Vue 的响应式系统
  • 类型安全:完整的 TypeScript 支持,保留了类型推断
  • 简洁直观的 API 设计

缺点

  • 灵活性受限
  • 调试复杂性提升

customRef谨慎使用

当使用 customRef 时,我们应该谨慎对待其 getter 的返回值,尤其是在每次运行 getter 时都生成新对象数据类型的情况下。当这样的 customRef 作为 prop 传递时,将影响父组件和子组件之间的关系。

父组件的渲染函数可能会被其他的响应式状态变化触发。在重新渲染过程中,我们会重新评估 customRef 的值,并返回一个新的对象数据类型作为子组件的 prop。这个 prop 会与其上一个值进行比较,由于两者不同,子组件中 customRef 的响应式依赖将被触发。与此同时,因为没有调用 customRef 的 setter,父组件中的响应式依赖不会运行。

在演练场中尝试一下