一句话总结核心区别:
- Utils(工具函数) :纯数据 / 逻辑处理,无状态、无 Vue 特性,通用、可在任何框架 / 无框架使用。
- Composables(组合式函数) :Vue 专属的状态化逻辑封装,依赖 Vue API(ref/reactive/watch 等),带响应式状态、生命周期,只能在 Vue 组件 / 其他 Composables 中使用。
一、Utils(工具函数)详解
1. 定义
utils 是通用工具函数 ,本质是纯函数(优先) ,用来封装与框架无关的通用逻辑,只负责「输入 → 处理 → 输出」,不依赖任何前端框架(Vue/React/ 原生 JS 都能用)。
2. 核心特点
- 无 Vue 依赖 :不使用
ref/reactive/watch/生命周期等任何 Vue API - 无状态 / 无副作用:相同输入永远返回相同输出,不修改外部变量
- 通用性极强:可在 Vue 组件、JS 文件、Node.js 中任意调用
- 轻量、独立:一个函数只做一件事(格式化、计算、校验等)
3. 适用场景
- 数据格式化(时间、金额、手机号)
- 字符串 / 数组 / 对象操作
- 表单校验、数学计算
- 本地存储封装(简单版)、通用常量
4. 代码示例
Javascript
js
// src/utils/format.js
// 纯工具函数:和 Vue 无关,任何项目都能用
export function formatMoney(num) {
if (isNaN(num)) return '0.00'
return Number(num).toFixed(2)
}
export function formatDate(timestamp) {
const date = new Date(timestamp)
return date.toLocaleDateString()
}
TypeScript
ts
// src/utils/format.ts
// 纯工具函数:和 Vue 无关,任何项目都能用
/**
* 格式化金额(保留两位小数)
* @param num - 数字或数字字符串
* @returns 格式化后的金额字符串
*/
export function formatMoney(num: number | string): string {
const n = typeof num === 'string' ? parseFloat(num) : num
if (isNaN(n)) return '0.00'
return n.toFixed(2)
}
/**
* 格式化时间戳为本地日期字符串
* @param timestamp - 毫秒时间戳
* @returns 格式化后的日期字符串
*/
export function formatDate(timestamp: number): string {
const date = new Date(timestamp)
return date.toLocaleDateString()
}
5. 使用方式
Javascript
js
<script setup>
// 直接调用,无需关心生命周期、响应式
import { formatMoney } from '@/utils/format'
const price = 99
console.log(formatMoney(price)) // 99.00
</script>
TypeScript
ts
<script setup lang="ts">
import { formatMoney } from '@/utils/format'
const price = 99
console.log(formatMoney(price)) // "99.00"
</script>
二、Composables(组合式函数)详解
1. 定义
composables 是Vue 组合式函数 ,专门用来封装带 Vue 响应式 / 生命周期的复用逻辑,是 Vue 3 组合式 API 的核心实践。
命名规范:必须以 use开头 (如 useUser、useWindowSize)。
2. 核心特点
- 强依赖 Vue :必须使用 Vue 提供的 API(
ref/reactive/watch/onMounted等) - 自带响应式状态:内部管理响应式数据,可被组件共享、追踪变化
- 绑定 Vue 生命周期:能监听组件挂载、卸载,处理副作用
- 组件专属 :只能在
<script setup>或其他 Composables 中使用 - 逻辑聚合:把「数据 + 方法 + 监听」打包复用
3. 适用场景
- 响应式数据逻辑(窗口大小、滚动位置、计时器)
- 业务状态共享(用户信息、购物车、表单状态)
- 生命周期副作用(定时器、事件监听、接口轮询)
- 跨组件复用的 Vue 专属逻辑
4. 代码示例
Javascript
js
// src/composables/useWindowSize.js
// Vue 专属组合式函数:依赖响应式 + 生命周期
import { ref, onMounted, onUnmounted } from 'vue'
export function useWindowSize() {
// 响应式状态(Vue API)
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
// 事件处理函数
function updateSize() {
width.value = window.innerWidth
height.value = window.innerHeight
}
// 绑定 Vue 生命周期
onMounted(() => window.addEventListener('resize', updateSize))
onUnmounted(() => window.removeEventListener('resize', updateSize))
// 返回响应式状态和方法
return { width, height }
}
TypeScript
ts
// src/composables/useWindowSize.ts
// Vue 专属组合式函数:依赖响应式 + 生命周期
import { ref, onMounted, onUnmounted, type Ref } from 'vue'
interface WindowSize {
width: Ref<number>
height: Ref<number>
}
export function useWindowSize(): WindowSize {
// 响应式状态(Vue API)
const width = ref<number>(window.innerWidth)
const height = ref<number>(window.innerHeight)
// 事件处理函数
function updateSize(): void {
width.value = window.innerWidth
height.value = window.innerHeight
}
// 绑定 Vue 生命周期
onMounted(() => window.addEventListener('resize', updateSize))
onUnmounted(() => window.removeEventListener('resize', updateSize))
// 返回响应式状态
return { width, height }
}
5. 使用方式
Javascript
js
<script setup>
// 直接使用响应式数据,自动跟随变化
import { useWindowSize } from '@/composables/useWindowSize'
const { width, height } = useWindowSize()
</script>
<template>
<div>窗口宽度:{{ width }}</div>
<div>窗口高度:{{ height }}</div>
</template>
TypeScript
ts
<script setup lang="ts">
import { useWindowSize } from '@/composables/useWindowSize'
const { width, height } = useWindowSize()
</script>
<template>
<div>窗口宽度:{{ width }}</div>
<div>窗口高度:{{ height }}</div>
</template>
三、Composables 和 Utils 核心区别对比表
| 维度 | Utils(工具函数) | Composables(组合式函数) |
|---|---|---|
| 依赖 | 无框架依赖,纯 JS | 强依赖 Vue 3 组合式 API |
| 状态 | 无状态、纯函数 | 有响应式状态,可管理数据 |
| 生命周期 | 无关,不依赖组件生命周期 | 可使用 onMounted 等 Vue 生命周期 |
| 复用范围 | 全场景通用(Vue/React/Node/ 原生 JS) | 仅 Vue 组件 / 其他 Composables 中使用 |
| 核心作用 | 数据处理、工具方法 | 封装带响应式的业务逻辑、组件逻辑复用 |
| 命名 | 随意(camelCase) | 强制 useXxx 开头 |
| 例子 | formatMoney 、debounce 、validatePhone |
useUser 、useWindowSize 、useForm |
四、最简单的区分方法
- 如果逻辑里用到了
ref/reactive/watch/onMounted→ 一定放在composables - 如果只是纯数据计算、格式化、通用方法 → 一定放在
utils - Composables 可以调用 Utils,但 Utils 绝对不能调用 Composables(工具不能依赖框架)
五、标准项目目录结构
plaintext
plain
src/
├── composables/ # Vue 组合式函数(响应式、生命周期)
│ ├── useUser.js
│ ├── useWindowSize.js
│ └── useCart.js
├── utils/ # 通用工具函数(纯 JS、无状态)
│ ├── format.js
│ ├── validate.js
│ └── common.js
总结
- Utils = 通用工具,纯 JS,无状态,哪里都能用
- Composables = Vue 逻辑复用,带响应式 + 生命周期,只能在 Vue 中用
- 区分关键:是否使用 Vue API、是否需要响应式状态