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 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
ShineWinsu4 小时前
对于JavaScript:操作元素的解析
开发语言·前端·javascript·面试·web·dom·元素
福兮说5 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json
程序员老赵7 小时前
Docker 部署青龙面板:轻松搭建定时任务管理平台
前端·docker·开源
Swot7 小时前
Asciidoctor 公式数字下标错位问题:原因分析与解决方案
前端
Swot7 小时前
leo-editor 中将 Markdown 内容转成 asciidoc 格式
前端
狒狒AI7 小时前
Remotion / GSAP+Three / HyperFrames / Skia 四栈实测:代码渲染知识视频的选栈方法(附开源技能)
前端·人工智能·aigc
修炼的dance7 小时前
浏览器里导出H.265,8.5秒和55.5秒差在硬件
前端
补码缩写7 小时前
WebCodecs选编码器,探测之后还要验解码和播放
前端
大文说跨境7 小时前
亚马逊多账号管理浏览器怎么选?云登/紫鸟/AdsPower 对比
java·前端·自动化