Vue3 + TypeScript 的 Hooks 实用示例

示例 1: 防抖 Hook(useDebounce)

typescript

复制代码
// hooks/useDebounce.ts
import { ref, watch, onUnmounted, type WatchSource } from 'vue';

/**
 * 防抖 Hook
 * @param source 监听的响应式数据源
 * @param callback 防抖后执行的回调函数
 * @param delay 防抖延迟时间(毫秒,默认 300ms)
 */
export function useDebounce<T>(
  source: WatchSource<T>,
  callback: (value: T) => void,
  delay: number = 300
) {
  let timeoutId: number | null = null;

  // 监听数据源变化
  watch(source, (newValue) => {
    // 清除之前的定时器
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    // 设置新的定时器
    timeoutId = setTimeout(() => {
      callback(newValue);
    }, delay);
  });

  // 组件卸载时清除定时器
  onUnmounted(() => {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
  });
}

// 组件中使用示例:
// const searchQuery = ref('');
// useDebounce(searchQuery, (value) => {
//   console.log('防抖后的搜索值:', value);
// });

示例 2: 本地存储 Hook(useLocalStorage)

typescript

复制代码
// hooks/useLocalStorage.ts
import { ref, watchEffect, type Ref } from 'vue';

/**
 * 本地存储 Hook
 * @param key 存储的键名
 * @param defaultValue 默认值
 * @returns 返回响应式变量和更新函数
 */
export function useLocalStorage<T>(
  key: string,
  defaultValue: T
): [Ref<T>, (value: T) => void] {
  // 从 localStorage 读取初始值
  const storedValue = localStorage.getItem(key);
  const value = ref<T>(
    storedValue ? JSON.parse(storedValue) : defaultValue
  ) as Ref<T>;

  // 监听变化并保存到 localStorage
  watchEffect(() => {
    localStorage.setItem(key, JSON.stringify(value.value));
  });

  // 提供更新函数(可选,直接修改 value 也有效)
  const updateValue = (newValue: T) => {
    value.value = newValue;
  };

  return [value, updateValue];
}

// 组件中使用示例:
// const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light');

示例 3: 网络请求 Hook(useFetch)

typescript

复制代码
// hooks/useFetch.ts
import { ref, type Ref } from 'vue';

interface UseFetchReturn<T> {
  data: Ref<T | null>;
  error: Ref<Error | null>;
  loading: Ref<boolean>;
  execute: () => Promise<void>;
}

/**
 * 网络请求 Hook
 * @param url 请求地址
 * @param options 请求配置(可选)
 */
export function useFetch<T = unknown>(
  url: string,
  options?: RequestInit
): UseFetchReturn<T> {
  const data = ref<T | null>(null);
  const error = ref<Error | null>(null);
  const loading = ref(false);

  // 执行请求的函数
  const execute = async () => {
    loading.value = true;
    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      data.value = await response.json();
      error.value = null;
    } catch (err) {
      error.value = err as Error;
      data.value = null;
    } finally {
      loading.value = false;
    }
  };

  return { data, error, loading, execute };
}

// 组件中使用示例:
// const { data, error, loading, execute } = useFetch<User[]>('/api/users');
// execute(); // 手动触发请求

示例 4: 倒计时 Hook(useCountdown)

typescript

复制代码
// hooks/useCountdown.ts
import { ref, onUnmounted } from 'vue';

/**
 * 倒计时 Hook
 * @param seconds 倒计时总秒数
 * @returns 剩余时间(秒)和开始/重置函数
 */
export function useCountdown(seconds: number) {
  const count = ref(seconds);
  let timer: number | null = null;

  // 开始/重置倒计时
  const start = () => {
    reset(); // 重置倒计时
    timer = setInterval(() => {
      if (count.value > 0) {
        count.value--;
      } else {
        clearInterval(timer!);
      }
    }, 1000);
  };

  // 重置倒计时
  const reset = () => {
    if (timer) {
      clearInterval(timer);
      timer = null;
    }
    count.value = seconds;
  };

  // 组件卸载时清除定时器
  onUnmounted(reset);

  return { count, start, reset };
}

// 组件中使用示例:
// const { count, start } = useCountdown(60);
// start(); // 开始倒计时

示例 5: 事件监听 Hook(useEventListener)

typescript

复制代码
// hooks/useEventListener.ts
import { onMounted, onUnmounted } from 'vue';

type EventTarget = Window | Document | HTMLElement;

/**
 * 事件监听 Hook
 * @param target 目标元素(默认 window)
 * @param event 事件名称
 * @param listener 事件回调
 */
export function useEventListener(
  event: string,
  listener: EventListener,
  target: EventTarget = window
) {
  // 挂载时添加监听
  onMounted(() => {
    target.addEventListener(event, listener);
  });

  // 卸载时移除监听
  onUnmounted(() => {
    target.removeEventListener(event, listener);
  });
}

// 组件中使用示例:
// useEventListener('click', (e) => {
//   console.log('全局点击事件', e);
// });

最佳实践建议:

  1. 类型安全:

    • 使用 TypeScript 接口 (interface) 明确函数参数和返回值类型。

    • 用泛型 (<T>) 处理动态数据类型(如 useFetch 中的响应数据)。

  2. 响应式处理:

    • 优先使用 ref 替代 reactive(更适合类型推导)。

    • 使用 watch 或 watchEffect 处理副作用。

  3. 资源清理:

    • 在 onUnmounted 中清除定时器、事件监听等资源。
  4. 组合复用:

    • 多个简单 Hook 可以组合成复杂逻辑(如 useCountdown + useEventListener)。
  5. 推荐工具库:

    • VueUse 提供了大量高质量的类型安全 Hooks。

通过这些示例,你可以逐步掌握如何编写类型安全、可复用的 Vue3 Hooks。实际开发中,根据需求灵活组合这些基础 Hooks,能显著提升代码质量和开发效率!

相关推荐
卸任2 小时前
Vibe coding工具使用心得
前端·ai编程
brien93 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance3 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M4 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写4 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李4 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易4 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马4 小时前
前沿视界
前端
kyriewen4 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易4 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员