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,能显著提升代码质量和开发效率!

相关推荐
剪刀石头布啊8 小时前
js中改变this指向的操作有哪些
前端
剪刀石头布啊8 小时前
vue、react 列表中使用 index 作为 key 的话,修改某一个元素后会发生什么
前端
方方洛8 小时前
glowglow:语言无关的语法高亮器是怎么实现的
前端·前端框架
java_nnnn9 小时前
JavaEE进阶-HTML基础认识
前端·java-ee·html
鬼手点金9 小时前
Claude Code示范案例-常用快捷命令
java·服务器·前端·计算机视觉·前向传播
CopyCode9 小时前
我排查了一下午,发现项目打包体积翻倍的元凶是它
前端·性能优化
u0111026759 小时前
Vue 3 实现图片裁剪框:拖动、缩放与固定宽高比
前端·javascript·vue.js
汉堡大王952710 小时前
一张图三句需求,我用 Trae Work 做了一块能看日出日落和月相的天文机械表
前端·后端·github
小小善后师10 小时前
用 Canvas + AI 实现登录页的 Logo 粒子动画
前端·vue.js
Amos_Web10 小时前
Rspack 源码解析(十六):多类型资源如何进入 Compilation.assets
前端·rust·前端框架