Vue3:自定义Hooks

582 阅读4分钟

一、Vue2与Vue3 API对比及Hooks特性

1. Vue2 Option API vs Vue3 Composition API

特性Vue2 Option APIVue3 Composition API
代码结构数据、方法、生命周期分散在选项中逻辑集中于setup()函数,代码组织更灵活
复用性依赖Mixins,易引发命名冲突与逻辑混乱通过自定义Hooks封装逻辑,实现高复用性
响应式系统基于Object.defineProperty基于Proxy,支持数组/对象全响应
类型推断与TypeScript兼容性较差与TypeScript深度集成,类型安全更完善
性能优化需手动优化(如避免重复计算)静态分析+Tree-shaking优化,性能更高效

优点

  • Option API:结构直观,适合小型项目或快速开发。
  • Composition API:逻辑复用性强,适合复杂业务场景,代码可维护性更高。

缺点

  • Option API:逻辑分散导致维护困难,Mixins易冲突。
  • Composition API:学习成本较高,需理解响应式API(如refreactive)。

2. Hooks与传统函数封装的区别

特性传统工具函数Vue3 Hooks
响应式依赖无响应式变量,无法自动触发视图更新内部使用refreactive等响应式API
副作用管理需手动管理副作用(如定时器、事件监听)通过onMountedwatch等生命周期钩子
数据共享无法直接共享组件间数据可通过模块作用域实现全局状态管理
使用场景纯计算逻辑(如加减法)封装复杂逻辑(如表单验证、数据拉取)

示例对比

// 传统工具函数
function add(a, b) { return a + b; }

// Vue3 Hook
import { ref } from 'vue';
function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const increment = () => count.value++;
  return { count, increment };
}

二、Vue3 Hooks的定义与核心原理

1. 定义

Vue3 Hooks是基于组合式API(Composition API)的自定义函数,用于封装可复用的响应式逻辑与副作用。其核心特性包括:

  • 响应式数据:通过refreactive创建响应式变量。
  • 副作用管理:利用onMountedonUnmounted等生命周期钩子管理副作用。
  • 逻辑复用:通过函数调用将逻辑拆分为独立模块。

2. 核心原理

Hooks依赖于Vue3的响应式系统(基于Proxy)和闭包机制

  • 每次调用Hook函数时,会生成独立的响应式数据与副作用实例。
  • 通过模块作用域(如useGlobalMouse.js)可实现全局状态共享。

三、Vue3源码中的Hooks使用案例

1. useMouse:监听鼠标位置

import { ref, onMounted, onUnmounted } from 'vue';

export function useMouse() {
  const x = ref(0);
  const y = ref(0);

  function update(e) {
    x.value = e.pageX;
    y.value = e.pageY;
  }

  onMounted(() => window.addEventListener('mousemove', update));
  onUnmounted(() => window.removeEventListener('mousemove', update));

  return { x, y };
}

2. useCounter:封装计数逻辑

import { ref } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const increment = () => count.value++;
  const decrement = () => count.value--;
  return { count, increment, decrement };
}

3. useFetch:数据拉取与缓存

import { ref, onMounted } from 'vue';

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);

  onMounted(async () => {
    try {
      const res = await fetch(url);
      data.value = await res.json();
    } catch (err) {
      error.value = err;
    }
  });

  return { data, error };
}

四、实际场景中的Hooks应用

1. 场景:表单验证

需求:用户注册表单需验证邮箱格式与密码强度。
实现

// hooks/useFormValidation.ts
import { ref } from 'vue';

export function useFormValidation() {
  const isValidEmail = ref(false);
  const isStrongPassword = ref(false);

  const validateEmail = (email) => {
    const pattern = /^[^\s@]+@[^\s@]+.[^\s@]+$/;
    isValidEmail.value = pattern.test(email);
  };

  const validatePassword = (password) => {
    const pattern = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;
    isStrongPassword.value = pattern.test(password);
  };

  return { isValidEmail, isStrongPassword, validateEmail, validatePassword };
}

调用示例

<template>
  <input v-model="email" @input="validateEmail(email)" />
  <p v-if="!isValidEmail">邮箱格式不正确</p>
</template>

<script setup>
import { useFormValidation } from './hooks/useFormValidation';

const { isValidEmail, validateEmail } = useFormValidation();
</script>

2. 场景:购物车状态管理

需求:跨组件共享购物车商品列表与总价。
实现

// hooks/useCart.ts
import { reactive } from 'vue';

export function useCart() {
  const cartItems = reactive([]);
  const totalPrice = reactive(0);

  const addToCart = (item) => {
    cartItems.push(item);
    totalPrice += item.price;
  };

  const removeFromCart = (index) => {
    totalPrice -= cartItems[index].price;
    cartItems.splice(index, 1);
  };

  return { cartItems, totalPrice, addToCart, removeFromCart };
}

调用示例

<template>
  <div>总价:{{ totalPrice }}</div>
  <button @click="addToCart({ name: '商品A', price: 10 })">添加商品</button>
</template>

<script setup>
import { useCart } from './hooks/useCart';

const { cartItems, totalPrice, addToCart } = useCart();
</script>

3. 场景:实时搜索建议

需求:输入关键词时,从API获取搜索建议并展示。
实现

// hooks/useSearch.ts
import { ref, watch } from 'vue';

export function useSearch(apiUrl) {
  const query = ref('');
  const results = ref([]);

  const fetchData = async (q) => {
    const res = await fetch(`${apiUrl}?q=${q}`);
    results.value = await res.json();
  };

  watch(query, (newQuery) => {
    if (newQuery.length >= 2) fetchData(newQuery);
  });

  return { query, results };
}

调用示例

<template>
  <input v-model="query" placeholder="输入关键词" />
  <ul>
    <li v-for="item in results" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

<script setup>
import { useSearch } from './hooks/useSearch';

const { query, results } = useSearch('https://api.example.com/search');
</script>

五、Hooks的扩展性:替代Vuex/Pinia的可能

1. 全局状态管理

通过模块作用域响应式共享,Hooks可实现轻量级全局状态管理:


// store.js
import { reactive } from 'vue';

const state = reactive({
  theme: 'light',
  toggleTheme: () => {
    state.theme = state.theme === 'light' ? 'dark' : 'light';
  }
});

export default state;

2. 与Vuex/Pinia的对比

特性Hooks全局状态Vuex/Pinia
状态隔离依赖模块作用域集中式状态仓库
中间件支持无内置中间件支持中间件(如日志、异步操作)
代码侵入性低(无需额外配置)高(需引入插件并定义store)
适用场景简单状态共享(如主题切换)复杂状态管理(如用户登录、权限控制)

3. 潜在问题

  • 状态污染:未合理设计模块作用域可能导致状态冲突。
  • 性能瓶颈:频繁更新响应式数据可能影响性能。