Code前端首页关于Code前端联系我们

1.Vue3 里的「use」到底是干啥的?

terry 12小时前 阅读数 16 #Vue
文章标签 Vue3;use

刚上手Vue3的同学,大概率会对「use」这个词犯迷糊:为啥很多代码里函数都叫useXXX?自己咋写这种use开头的逻辑?它和普通函数有啥区别?别慌,这篇文章把Vue3里use的用法、自定义实践、常见场景全拆明白,看完你也能玩转逻辑复用~

Vue3 本身没有“官方内置的 use 函数”,但**以 use 开头命名的函数**是社区和官方推荐的「自定义 Hooks」写法(基于 Composition API 的逻辑复用思路),简单说,它是**把组件中可复用的逻辑(比如请求数据、监听事件、状态管理)封装成独立函数**,让多个组件能共享这部分逻辑,同时和 Vue 的响应式、生命周期深度绑定。

举个例子:做“鼠标位置跟踪”功能时,把“监听 mousemove、存储坐标、销毁事件”这些逻辑放到 useMouse.js 里,其他组件要跟踪鼠标时,直接 import { useMouse } from './hooks' 调用,就能拿到实时坐标——不用在每个组件重复写事件绑定和解绑代码,这就是复用的价值。

核心特点是:和 Vue 的响应式(ref/reactive)、生命周期钩子(onMounted/onUnmounted 等)深度整合,复用的不仅是代码,更是“响应式状态 + 副作用逻辑”的组合。

自己咋写一个 use 开头的自定义 Hook?

写自定义 Hook 就像“把组件里的一段逻辑拆出来,做成可插拔的‘逻辑插件’”,以“跟踪鼠标位置”为例,手把手写一个 useMousePosition

// src/hooks/useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
  // 1. 用 ref 定义响应式状态(存储鼠标坐标)
  const x = ref(0)
  const y = ref(0)
  // 2. 定义副作用逻辑(绑定/解绑事件)
  function handleMouseMove(e) {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', handleMouseMove)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', handleMouseMove)
  })
  // 3. 返回要暴露给组件的状态
  return { x, y }
}

然后在组件中使用:

<script setup>
import { useMousePosition } from '@/hooks/useMousePosition'
const { x, y } = useMousePosition()
</script>
<template>
  <div>鼠标X:{{ x }},Y:{{ y }}</div>
</template>

写 Hook 的关键思路

  • 状态必须用 ref/reactive 包裹,保证数据变化能触发组件更新;
  • 生命周期钩子(如 onMounted onUnmounted)要和逻辑绑定,确保组件销毁时清理副作用(比如事件解绑);
  • 返回值按需暴露,让组件只拿需要的状态/方法,保持封装性。

再拓展个场景:写 useFetch 封装请求逻辑(用 refloading error dataonMounted 发请求),组件调用后直接用这些状态,代码会清爽很多。

自定义 Hook 和普通函数有啥区别?

很多同学疑惑:“直接写普通函数封装逻辑不行吗?” 还真不一样,核心差异在“和 Vue 生态的结合度”

对比维度 自定义 Hook 普通函数
响应式关联 ref/reactive 包状态,和组件响应式连接 返回普通值/对象,修改后组件不更新
生命周期绑定 onMounted 等钩子,和组件生命周期同步 写生命周期钩子无效(没和组件关联)
逻辑复用粒度 封装“状态 + 副作用 + 逻辑”的组合包 仅复用纯逻辑,状态需组件自己维护

举个反面例子:如果把 useMousePosition 写成普通函数,事件监听绑了但没在组件卸载时销毁,多个组件调用会导致事件重复绑定,页面卡顿甚至崩溃(内存泄漏),而 Hook 里的 onUnmounted 会自动和组件生命周期挂钩,组件销毁时自动清理。

实际项目中,use Hooks 能解决哪些场景?

从业务开发到工具封装,use Hooks 应用场景极广,挑几个高频场景拆解:

场景1:网络请求逻辑复用

每个页面都写“loading、error、发请求”的重复代码?用 useFetch 统一封装:

// src/hooks/useFetch.js
import { ref, onMounted } from 'vue'
import axios from 'axios'
export function useFetch(url) {
  const data = ref(null)
  const loading = ref(true)
  const error = ref(null)
  onMounted(async () => {
    loading.value = true
    try {
      const res = await axios.get(url)
      data.value = res.data
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  })
  return { data, loading, error }
}

组件中使用:

<script setup>
import { useFetch } from '@/hooks/useFetch'
const { data, loading, error } = useFetch('https://api.example.com/data')
</script>
<template>
  <div v-if="loading">加载中...</div>
  <div v-else-if="error">请求失败:{{ error.message }}</div>
  <div v-else>数据:{{ data }}</div>
</template>

好处是:所有请求的 loading、错误处理逻辑集中维护,新页面请求时直接调用,不用重复写 try/catch 和状态管理。

场景2:轻量状态管理(不用 Vuex/Pinia 也能共享逻辑)

比如用户信息的获取和更新,用 useUser 封装:

// src/hooks/useUser.js
import { ref, computed } from 'vue'
// 从 localStorage 取初始用户信息
const userInfo = ref(JSON.parse(localStorage.getItem('user')) || { name: '游客' })
export function useUser() {
  // 状态:用户信息
  const user = userInfo
  // 计算属性:是否登录
  const isLogin = computed(() => user.value.id)
  // 方法:更新用户信息
  function updateUser(newInfo) {
    user.value = { ...user.value, ...newInfo }
    localStorage.setItem('user', JSON.stringify(user.value))
  }
  return { user, isLogin, updateUser }
}

组件 A 修改用户信息,组件 B 能实时拿到最新值——因为 userref 响应式数据,多个组件调用 useUser 拿到的是同一个 ref 的引用,天然实现“跨组件状态同步”(比 Vuex 轻量,适合中小项目)。

场景3:浏览器 API 封装(让组件更干净)

比如封装 localStorage 的读写,用 useLocalStorage 自动同步响应式状态:

// src/hooks/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, initialValue) {
  // 从 localStorage 取数据,无则用初始值
  const storedValue = localStorage.getItem(key)
  const value = ref(storedValue ? JSON.parse(storedValue) : initialValue)
  // 监听 value 变化,同步到 localStorage
  watch(value, (newVal) => {
    localStorage.setItem(key, JSON.stringify(newVal))
  }, { deep: true }) // 复杂对象需深监听
  return value
}

组件中使用:

<script setup>
import { useLocalStorage } from '@/hooks/useLocalStorage'
const theme = useLocalStorage('app-theme', 'light') // 自动读写 localStorage
</script>
<template>
  <button @click="theme = theme === 'light' ? 'dark' : 'light'">
    切换主题:{{ theme }}
  </button>
</template>

组件不用关心 localStorage 读写细节,逻辑全在 Hook 里,维护更省心。

场景4:交互与动画逻辑复用

比如做“可拖拽元素”功能,用 useDraggable 封装:

// src/hooks/useDraggable.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useDraggable(targetRef) { // 接收元素的 ref
  const x = ref(0)
  const y = ref(0)
  let startX = 0, startY = 0
  function handleMouseDown(e) {
    startX = e.pageX - x.value
    startY = e.pageY - y.value
    window.addEventListener('mousemove', handleMouseMove)
    window.addEventListener('mouseup', handleMouseUp)
  }
  function handleMouseMove(e) {
    x.value = e.pageX - startX
    y.value = e.pageY - startY
  }
  function handleMouseUp() {
    window.removeEventListener('mousemove', handleMouseMove)
    window.removeEventListener('mouseup', handleMouseUp)
  }
  onMounted(() => {
    targetRef.value.addEventListener('mousedown', handleMouseDown)
  })
  onUnmounted(() => {
    targetRef.value.removeEventListener('mousedown', handleMouseDown)
  })
  return { x, y }
}

组件中结合 ref 使用:

<script setup>
import { ref } from 'vue'
import { useDraggable } from '@/hooks/useDraggable'
const boxRef = ref(null)
const { x, y } = useDraggable(boxRef)
</script>
<template>
  <div ref="boxRef" :style="{ position: 'fixed', left: x + 'px', top: y + 'px' }">
    拖拽我~
  </div>
</template>

不管多少个组件要做拖拽,都能复用 useDraggable,不用重复写事件绑定逻辑,代码量直接减半。

用 use Hooks 时容易踩哪些坑?咋避免?

写 Hook 爽,但稍不注意就会掉坑,分享几个高频踩坑点和解决方案:

坑1:忘记清理副作用,导致内存泄漏

比如在 Hook 里加了 window.addEventListener,但没在 onUnmountedremoveEventListener——组件销毁后事件还在,重复触发会让页面越来越卡。

解决所有绑定的事件、定时器、订阅等,必须在 onUnmounted 里销毁,像 useMousePosition 的例子,onMounted 绑定 mousemoveonUnmounted 就解绑,确保组件销毁后逻辑也清理干净。

坑2:响应式状态“失效”,修改后组件不更新

Hook 里返回的是普通对象/值,而非 ref/reactive 包裹的:

// 错误示例:返回普通对象,组件拿不到更新
export function useBadHook() {
  let count = 0
  function increment() { count++ }
  return { count, increment } // count 是普通变量,修改后组件不更新
}

解决所有要让组件响应的状态,必须用 refreactive 包装,改成:

export function useGoodHook() {
  const count = ref(0)
  function increment() { count.value++ }
  return { count, increment } // count 是 ref,修改会触发更新
}

坑3:变量命名冲突,Hook 内部状态互相干扰

比如两个 Hook 里都定义了叫 xref,组件同时调用时,若逻辑依赖全局状态,就会串数据。

解决Hook 内部状态尽量用局部变量(ref/reactive 在函数内定义),避免依赖全局状态,若需跨 Hook 共享,考虑用 Pinia/Vuex,或专门的状态管理 Hook(如前面 useUser 的例子,用 ref 在模块内共享)。

坑4:过度封装,把简单逻辑搞复杂

比如一个组件里只用一次的逻辑,硬拆成 Hook,反而增加维护成本。

解决只对“真正复用 2 次以上”的逻辑做 Hook 封装,如果是组件内私有逻辑,直接写在 setup 里更直观。

Vue3 的 use 和 React Hooks 有啥不一样?

很多从 React 转 Vue 的同学好奇这个问题,核心差异来自框架设计:

  1. 响应式原理:Vue 基于 Proxy 自动响应式,修改 ref.valuereactive 对象属性会自动触发更新;React 基于“依赖数组 + 手动触发”(useState/useEffect 的依赖),Vue 的 Hook 不用写依赖数组,更省心。
  2. 执行时机与关联:Vue 的 Hook 执行和组件实例强关联,生命周期钩子(onMounted 等)自动绑定到当前组件;React 的 Hooks 按顺序调用,依赖调用顺序保持状态,容易因顺序错误导致 Bug。
  3. 闭包问题:React 的 useEffect 容易因闭包捕获旧值踩坑;Vue 的响应式自动追踪依赖,Hook 里的逻辑更接近“同步写法”,很少遇到闭包导致的状态滞后问题。

简单说:Vue 的 use Hooks 写起来更自由,不用像 React 那样小心翼翼处理依赖和顺序,对新手更友好~

项目里咋组织这些 use Hooks 更合理?

随着项目变大,Hook 越来越多,得有好的目录结构和规范,否则找代码像大海捞针,分享实用组织方式:

  1. 按功能分类建文件夹:在 src 下新建 hooks 文件夹,再分子文件夹:

    • network:放 useFetchuseRequest 等请求相关 Hook;
    • ui:放 useDraggableuseModal(弹窗逻辑)等 UI 交互 Hook;
    • state:放 useUseruseCart(购物车状态)等状态管理 Hook;
    • browser:放 useLocalStorageuseResize(监听窗口 resize)等浏览器 API Hook。
  2. 单个 Hook 一个文件useMousePosition.js 单独放,避免一个文件塞多个 Hook 导致混乱。

  3. 用 TypeScript 增强类型:给 Hook 的参数、返回值加类型定义(useFetchurl 限定为字符串,返回的 data 加泛型),让 IDE 智能提示,减少 Bug。

  4. 写 README 说明用途:每个 Hook 文件夹或关键 Hook 文件里写简单注释,说明用途、用法、依赖,团队协作更高效。

举个目录结构例子:

src/
└── hooks/
    ├── network/
    │   └── useFetch.js
    ├── ui/
    │   └── useDraggable.js
    ├── state/
    │   └── useUser.js
    └── browser/
        └── useLocalStorage.js

这样不管自己维护还是新人接手,找逻辑、改逻辑都清晰很多。

Vue3 里的 use 自定义 Hooks 是「逻辑复用的核武器」,能让重复代码消失,组件更聚焦业务,从理解概念、动手写简单 Hook,到应对复杂场景、避坑优化,一步步练下来,你会发现项目代码量少了,可维护性却高了,现在就挑个重复逻辑,试着拆成 use Hook,感受下复用的快乐吧~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门