当前位置: 首页 > news >正文

Vue3中Hooks与普通函数的区别

在 Vue 3 中,组合式函数(Composition Functions,常被称为 Hooks) 和 普通函数 的区别主要体现在以下几个方面:


1. 依赖的上下文环境

  • 组合式函数
    必须运行在 Vue 的组件上下文中(如 setup() 或 <script setup>),因为它们依赖 Vue 的响应式系统(如 refreactive)和生命周期钩子(如 onMountedonUnmounted)。

    javascript

    // 组合式函数示例:useMouse
    import { ref, onMounted, onUnmounted } from 'vue';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 }; // 返回响应式数据
    }
  • 普通函数
    不依赖 Vue 的上下文,可以在任何地方调用(如工具函数、纯逻辑计算)。

    javascript

    // 普通函数示例:求和
    function add(a, b) {return a + b;
    }

2. 是否管理状态与副作用

  • 组合式函数
    封装有状态的逻辑,可能包含响应式数据、副作用(如事件监听、API 请求)及其清理逻辑。

    javascript

    // 管理副作用(生命周期钩子)
    onMounted(() => { /* ... */ });
    onUnmounted(() => { /* ... */ });
  • 普通函数
    通常是无状态的,不涉及响应式数据或副作用,仅处理输入并返回结果(纯函数)。

    javascript

    // 无状态逻辑:格式化日期
    function formatDate(date) {return new Date(date).toLocaleString();
    }

3. 返回值类型

  • 组合式函数
    通常返回响应式对象(如 refreactive)或包含响应式数据的结构,供组件消费。

    javascript

    const { x, y } = useMouse(); // x 和 y 是响应式的
  • 普通函数
    返回静态数据或非响应式结果。

    javascript

    const sum = add(1, 2); // 普通数字

4. 设计目的

  • 组合式函数
    实现逻辑复用,将组件中复杂的响应式逻辑抽离为独立单元,类似 React Hooks。

    javascript

    // 复用数据获取逻辑
    function useFetch(url) {const data = ref(null);fetch(url).then(res => data.value = res.json());return { data };
    }
  • 普通函数
    封装通用工具逻辑(如数据处理、算法),不涉及组件状态或生命周期。


5. 命名约定

  • 组合式函数
    社区约定以 use 开头(如 useFetchuseStorage),便于区分。

  • 普通函数
    无特殊命名要求。


总结对比表

特性组合式函数 (Hooks)普通函数
依赖上下文需在 Vue 组件上下文中调用可在任何地方调用
响应式数据操作 refreactive 等响应式 API不涉及响应式系统
生命周期钩子可调用 onMountedonUnmounted 等无法使用生命周期钩子
副作用管理封装副作用(如事件监听、API 请求)通常无副作用
返回值返回响应式数据或结构返回静态数据
目的复用有状态逻辑封装无状态工具逻辑

何时使用?

  • 用组合式函数:需要封装涉及响应式数据、生命周期或副作用的逻辑(如鼠标跟踪、API 请求)。

  • 用普通函数:处理纯数据转换、工具类操作(如日期格式化、数学计算)。

以下是一份使用 TypeScript 编写的 Vue 3 组合式函数(Hooks)的详细示例代码及解释,涵盖常见场景和高级用法:


示例 1:基础状态管理 Hook(useToggle

功能:封装布尔值的切换逻辑,支持类型安全的状态和方法。

typescript

复制

下载

// useToggle.ts
import { ref, type Ref } from 'vue';interface UseToggleReturn {state: Ref<boolean>;toggle: () => void;set: (value: boolean) => void;
}export function useToggle(initialValue: boolean = false): UseToggleReturn {const state = ref(initialValue);const toggle = () => {state.value = !state.value;};const set = (value: boolean) => {state.value = value;};return {state,toggle,set};
}
在组件中使用:

vue

复制

下载

<script setup lang="ts">
import { useToggle } from './useToggle';const { state: isDarkMode, toggle } = useToggle(true);
</script><template><button @click="toggle">{{ isDarkMode ? '☀️ Light' : '🌙 Dark' }} Mode</button>
</template>

关键点

  • 类型定义:使用 interface 明确定义返回值类型 UseToggleReturn

  • 泛型参数ref<boolean> 确保状态类型安全。

  • 代码提示:组件中使用时会自动提示 toggle 和 set 方法。


示例 2:带泛型的异步请求 Hook(useFetch

功能:封装数据请求逻辑,支持泛型类型推断。

typescript

复制

下载

// useFetch.ts
import { ref, type Ref } from 'vue';interface UseFetchReturn<T> {data: Ref<T | null>;error: Ref<string | null>;isLoading: Ref<boolean>;retry: () => Promise<void>;
}export function useFetch<T>(url: string): UseFetchReturn<T> {const data = ref<T | null>(null) as Ref<T | null>;const error = ref<string | null>(null);const isLoading = ref(false);const fetchData = async (): Promise<void> => {isLoading.value = true;try {const response = await fetch(url);if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);data.value = await response.json() as T;error.value = null;} catch (err: unknown) {error.value = err instanceof Error ? err.message : 'Unknown error';} finally {isLoading.value = false;}};fetchData();return {data,error,isLoading,retry: fetchData};
}
在组件中使用:

vue

复制

下载

<script setup lang="ts">
import { useFetch } from './useFetch';interface Post {id: number;title: string;content: string;
}const { data: posts, isLoading } = useFetch<Post[]>('https://api.example.com/posts');
</script><template><div v-if="isLoading">Loading...</div><ul v-else><li v-for="post in posts" :key="post.id">{{ post.title }}</li></ul>
</template>

关键点

  • 泛型参数useFetch<T> 允许在调用时指定数据类型(如 Post[])。

  • 类型断言data.value = await response.json() as T 确保类型正确。

  • 错误处理:使用 instanceof Error 安全地获取错误信息。


示例 3:联合类型和副作用管理的 Hook(useStorage

功能:封装 localStorage 操作,支持联合类型和自动同步。

typescript

复制

下载

// useStorage.ts
import { ref, watchEffect, type Ref } from 'vue';type StorageValue<T> = T | null;export function useStorage<T>(key: string,defaultValue: T
): {storedValue: Ref<StorageValue<T>>;setValue: (value: T) => void;removeValue: () => void;
} {const storedValue = ref<StorageValue<T>>(defaultValue);// 初始化时读取 localStoragetry {const item = localStorage.getItem(key);storedValue.value = item ? (JSON.parse(item) as T) : defaultValue;} catch (error) {console.error('Error reading localStorage:', error);}// 监听变化并同步到 localStoragewatchEffect(() => {try {if (storedValue.value === null) {localStorage.removeItem(key);} else {localStorage.setItem(key, JSON.stringify(storedValue.value));}} catch (error) {console.error('Error writing to localStorage:', error);}});const setValue = (value: T) => {storedValue.value = value;};const removeValue = () => {storedValue.value = null;};return {storedValue,setValue,removeValue};
}
在组件中使用:

vue

复制

下载

<script setup lang="ts">
import { useStorage } from './useStorage';const { storedValue: theme, setValue: setTheme } = useStorage<string>('app-theme', 'light');
</script><template><select :value="theme" @change="setTheme(($event.target as HTMLSelectElement).value)"><option value="light">Light</option><option value="dark">Dark</option></select>
</template>

关键点

  • 联合类型StorageValue<T> 表示值可以是 T 或 null

  • 类型安全操作setValue 强制接受 T 类型参数。

  • 错误边界:使用 try/catch 处理 localStorage 可能抛出的异常。


示例 4:复杂表单验证 Hook(useFormValidation

功能:类型安全的表单验证,支持动态规则和错误收集。

typescript

复制

下载

// useFormValidation.ts
import { ref, computed, type Ref } from 'vue';interface ValidationRule<T> {validator: (value: T) => boolean;message: string;
}interface UseFormValidationReturn<T extends Record<string, any>> {errors: Ref<Record<keyof T, string[]>>;validateField: <K extends keyof T>(field: K, value: T[K]) => void;isValid: Ref<boolean>;
}export function useFormValidation<T extends Record<string, any>>(rules: Record<keyof T, ValidationRule<T[keyof T]>[]>
): UseFormValidationReturn<T> {const errors = ref<Record<keyof T, string[]>>(Object.keys(rules).reduce((acc, key) => {acc[key as keyof T] = [];return acc;}, {} as Record<keyof T, string[]>));const validateField = <K extends keyof T>(field: K, value: T[K]): void => {errors.value[field] = [];rules[field].forEach((rule) => {if (!rule.validator(value)) {errors.value[field].push(rule.message);}});};const isValid = computed(() => {return Object.values(errors.value).every((e) => e.length === 0);});return {errors,validateField,isValid};
}
在组件中使用:

vue

复制

下载

<script setup lang="ts">
import { ref } from 'vue';
import { useFormValidation } from './useFormValidation';interface FormValues {email: string;password: string;
}const form = ref<FormValues>({email: '',password: ''
});const { errors, validateField, isValid } = useFormValidation<FormValues>({email: [{validator: (v) => v.includes('@'),message: 'Email must contain @'}],password: [{validator: (v) => v.length >= 6,message: 'Password must be at least 6 characters'}]
});
</script><template><input v-model="form.email" @blur="validateField('email', form.email)"/><div v-if="errors.email.length">{{ errors.email.join(', ') }}</div><inputv-model="form.password"type="password"@blur="validateField('password', form.password)"/><div v-if="errors.password.length">{{ errors.password.join(', ') }}</div><button :disabled="!isValid">Submit</button>
</template>

关键点

  • 泛型约束T extends Record<string, any> 确保传入的表单结构合法。

  • 动态字段类型validateField<K extends keyof T> 自动推断字段类型。

  • 类型安全规则ValidationRule<T[keyof T]>[] 确保验证规则与字段类型匹配。


高级场景:组合多个 Hooks(useUserDashboard

功能:组合数据请求、状态管理和事件监听。

typescript

复制

下载

// useUserDashboard.ts
import { computed, type Ref } from 'vue';
import { useFetch } from './useFetch';
import { useEventListener } from './useEventListener';interface User {id: number;name: string;email: string;
}interface UserDashboard {user: Ref<User | null>;isLoading: Ref<boolean>;error: Ref<string | null>;refresh: () => void;
}export function useUserDashboard(userId: number): UserDashboard {const { data: user, isLoading, error, retry } = useFetch<User>(`/api/users/${userId}`);// 监听窗口聚焦时刷新数据useEventListener(window, 'focus', retry);return {user,isLoading,error,refresh: retry};
}
在组件中使用:

vue

复制

下载

<script setup lang="ts">
import { useUserDashboard } from './useUserDashboard';const { user, isLoading } = useUserDashboard(123);
</script><template><div v-if="isLoading">Loading user...</div><div v-else><h1>{{ user?.name }}</h1><p>{{ user?.email }}</p></div>
</template>

关键点

  • 组合复用:通过 useFetch 和 useEventListener 构建复杂逻辑。

  • 自动刷新:窗口聚焦时自动重新请求数据。

  • 类型推断useFetch<User> 确保返回的用户数据符合 User 接口。


TypeScript 的优势总结

  1. 类型安全
    通过接口 (interface) 和泛型 (Generic) 明确数据类型,减少运行时错误。

  2. 代码提示
    自动补全返回值和方法,提升开发效率。

  3. 复杂类型处理
    使用 keyofextends 等高级类型操作,处理动态数据结构。

  4. 错误预防
    在编译阶段捕获类型不匹配问题(如尝试将字符串赋值给数字类型)。


最佳实践建议

  1. 始终定义接口
    为复杂数据结构和函数返回值编写 interface/type

  2. 合理使用泛型
    在需要动态类型的场景(如 useFetch<T>)中使用泛型。

  3. 类型断言谨慎使用
    仅在明确知道类型时使用 as(如 API 响应数据)。

  4. 利用工具类型
    使用 Partial<T>Pick<T, K> 等内置工具类型简化代码。

  5. 严格的 tsconfig.json
    启用 strict: true 等选项以最大化类型检查效益。


通过这些示例,你可以看到 TypeScript 如何显著提升 Vue 3 组合式函数的可靠性和开发体验。它不仅帮助捕获潜在错误,还能通过清晰的类型定义让代码更易于理解和维护。

相关文章:

  • 高效的CMS能帮助你快速建站。
  • 微机控制电液伺服钢轨滚动疲劳试验机
  • 喜马拉雅卖身腾讯音乐:在线音频独立时代的终结
  • shell(3)
  • 软件评测师考点重点知识
  • NdrpPointerUnmarshallInternal函数分析之pStubMsg--pAllocAllNodesContext的由来
  • vmare pro安装报错用户在命令行上发出了EULAS_AGREED=1,表示不接受许可协议的错误解决方法
  • MCP:如何通过模型控制推理助力AI模型实现“深度思考”?
  • timerfd定时器时间轮定时器
  • 机器学习:【抛掷硬币的贝叶斯后验概率】
  • 使用OpenAMP多核框架RPMsg实现高效控制和通信设计
  • 二极管钳位电路——Multisim电路仿真
  • 《Windows系统Java环境安装指南:从JDK17下载到环境变量配置》
  • leetcode 143. 重排链表
  • 解答UnityShader学习过程中的一些疑惑(持续更新中)
  • 在 Spring Boot 中实现异常处理的全面指南
  • Callable Future 实现多线程按照顺序上传文件
  • 知识付费平台推荐及对比介绍
  • 更新日期自动填充
  • ESG跨境电商怎么样?esg跨境电商有哪些功用?
  • 新疆维吾尔自治区原质量技术监督局局长刘新胜接受审查调查
  • 赛力斯拟赴港上市:去年扭亏为盈净利59亿元,三年内实现百万销量目标
  • 【社论】优化限购限行,激发汽车消费潜能
  • 日本大米价格连续16周上涨,再创最高纪录
  • 跨海论汉|专访白馥兰:对中国农业史的兴趣,从翻译《齐民要术》开始
  • 第一集丨《无尽的尽头》值得关注,《榜上佳婿》平平无奇