前言:从选项式到组合式的思维转变

        我们从前面开始讲ref到生命周期,今天我们来讲下vue3特有的功能hooks如果没有他就不算体验到vue3的组合式api了,代码有点多,不喜欢看代码的小伙伴可以看注释,接下来我们来看看:

        想象一下,你要开发一个简单的页面:上面有一个计数器,下面可以随机展示小狗图片。在 Vue 2 时代,你可能会这样写:

//vue
<template>
  <h2>当前求和为:{{ sum }}</h2>
  <button @click="increment">点我+1</button>
  <button @click="decrement">点我-1</button>
  <hr>
  <img v-for="(url, index) in dogList" :key="index" :src="url"> 
  <button @click="getDog">再来一只狗</button>
</template>

<script>
export default {
  data() {
    return {
      sum: 0,                    // 计数器数据
      dogList: []                // 小狗图片列表
    }
  },
  methods: {
    increment() {                // 计数器+1方法
      this.sum++
    },
    decrement() {                // 计数器-1方法  
    decrement() {                // 计数器-1方法
      this.sum--
    },
    async getDog() {             // 获取小狗图片方法
      const response = await fetch('https://dog.ceo/api/breed/pembroke/images/random')
      const data = await response.json()
      this.dogList.push(data.message)
    }
  },
  mounted() {
    this.getDog()               // 页面加载时自动获取一张图片
  }
}
</script>

这种写法看起来挺清晰的,但随着功能增加,问题就来了...

一、为什么要用 Hooks?一个现实的痛点

假如现在需求变了:

  1. 计数器需要添加"重置"功能

  2. 小狗图库需要添加加载状态和错误处理

  3. 还要在其他页面复用这些功能

传统的 Vue 2 写法就会变得很臃肿,而且代码无法复用

二、Vue 3 Hooks 的优雅解决方案

2.1 创建计数器 Hook

// hooks/useSum.ts
import { ref, onMounted } from 'vue'

// 创建一个专门管理计数器逻辑的 Hook
export default function useSum() {
  // 1. 定义计数器数据
  let sum = ref(0)

  // 2. 定义操作计数器的方法
  const increment = () => {
    sum.value += 1
  }
  
  const decrement = () => {
    sum.value -= 1
  }

  // 3. 组件挂载时的初始化逻辑
  onMounted(() => {
    increment() // 页面加载时默认+1
  })

  // 4. 把数据和方法"打包"返回给组件使用
  return { 
    sum,        // 当前计数值
    increment,  // +1 方法
    decrement   // -1 方法
  }
}

2.2 创建小狗图库 Hook

// hooks/useDog.ts
import { reactive } from 'vue'
import axios, { AxiosError } from 'axios'

// 创建一个专门管理小狗图片逻辑的 Hook
export default function useDog() {
  // 1. 定义小狗图库的完整状态
  let dogList = reactive<{
    urlList: string[],    // 图片URL列表
    isLoading: boolean,   // 加载状态
    error: string | null  // 错误信息
  }>({
    urlList: [],
    isLoading: false,
    error: null
  })

  // 2. 获取小狗图片的方法
  async function getDog() {
    try {
      dogList.isLoading = true    // 开始加载
      dogList.error = null        // 清空错误
      
      // 发送请求获取随机小狗图片
      let { data } = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
      
      // 将新图片添加到列表
      dogList.urlList.push(data.message)
      
    } catch (error) {
      // 错误处理
      const err = error as AxiosError
      dogList.error = err.message
      console.log('获取小狗图片失败:', err.message)
    } finally {
      dogList.isLoading = false   // 结束加载
    }
  }

  // 3. 向外部暴露数据和方法
  return {
    dogList,  // 图库状态(图片列表、加载状态、错误信息)
    getDog    // 获取新图片的方法
  }
}

2.3 在组件中使用 Hooks

vue

<template>
  <!-- 计数器区域 -->
  <h2>当前求和为:{{ sum }}</h2>
  <button @click="increment">点我+1</button>
  <button @click="decrement">点我-1</button>
  
  <hr>
  
  <!-- 小狗图库区域 -->
  <img v-for="(url, index) in dogList.urlList" :key="index" :src="url"> 
  <span v-show="dogList.isLoading">加载中......</span>
  <span v-show="dogList.error" style="color: red;">{{ dogList.error }}</span>
  <br>
  <button @click="getDog">再来一只狗</button>
</template>

<script setup lang="ts">
// 导入自定义 Hooks
import useSum from './hooks/useSum'
import useDog from './hooks/useDog'

// 使用计数器 Hook - 就像调用普通函数一样
let { sum, increment, decrement } = useSum()

// 使用小狗图库 Hook
let { dogList, getDog } = useDog()
</script>

三、Hooks 带来的巨大优势

3.1 代码复用:一次编写,到处使用

现在你可以在其他页面轻松复用这些功能:

vue

<!-- 另一个页面也想要计数器 -->
<script setup lang="ts">
import useSum from './hooks/useSum'

let { sum, increment } = useSum()
// 直接使用,无需重复编写计数器逻辑!
</script>

3.2 逻辑分离:各司其职,清晰明了

职责分离 传统写法 Hooks 写法
计数器逻辑 混在组件的 data/methods 中 独立的 useSum Hook
图库逻辑 混在组件的 data/methods 中 独立的 useDog Hook
组件本身 既要管UI又要管逻辑 只关心UI和Hook的组装

3.3 易于维护:修改逻辑不影响组件

如果要给计数器添加"重置"功能:

// 只需修改 useSum.ts,不影响任何使用它的组件
export default function useSum() {
  let sum = ref(0)

  const increment = () => { sum.value += 1 }
  const decrement = () => { sum.value -= 1 }
  
  // 新增重置功能
  const reset = () => { 
    sum.value = 0 
  }

  return { 
    sum, 
    increment, 
    decrement,
    reset  // 暴露新方法
  }
}

所有使用这个 Hook 的组件自动获得新功能!

四、Hooks 的设计原则

4.1 单一职责原则

  • useSum:只负责计数器逻辑

  • useDog:只负责小狗图库逻辑

  • 组件:只负责界面渲染和 Hook 组装

4.2 高内聚低耦合

  • 高内聚:相关逻辑聚集在同一个 Hook 中

  • 低耦合:Hooks 之间相互独立,可以任意组合

4.3 易于测试

// 可以单独测试 Hook,不需要渲染组件
test('useSum hook', () => {
  const { sum, increment } = useSum()
  
  expect(sum.value).toBe(0)
  increment()
  expect(sum.value).toBe(1)
})

五、从选项式到组合式的思维转变

Vue 2 思维:"这个功能应该放在哪个选项里?"

export default {
  data() {    // 数据放这里
    return { sum: 0, dogList: [] }
  },
  methods: {  // 方法放这里
    increment() { /* ... */ },
    getDog() { /* ... */ }
  },
  mounted() { // 初始化放这里
    this.getDog()
  }
}

Vue 3 思维:"这个功能如何封装成 Hook?"

// 计数器功能封装
export function useSum() {
  const sum = ref(0)
  const increment = () => sum.value++
  return { sum, increment }
}

// 图库功能封装  
export function useDog() {
  const dogList = ref([])
  const getDog = async () => { /* ... */ }
  return { dogList, getDog }
}

// 组件中组合使用
const { sum, increment } = useSum()
const { dogList, getDog } = useDog()

六、总结:为什么 Hooks 是 Vue 3 的精髓

通过这个简单的计数器和小狗图库例子,你应该能体会到:

  1. 更好的代码组织:相关逻辑聚集,不再碎片化

  2. 极强的复用性:一次封装,无限复用

  3. 易于维护:修改逻辑只需改动一个地方

  4. 便于测试:可以单独测试业务逻辑

  5. 清晰的责任划分:UI 与逻辑彻底分离

     Hooks 让 Vue 3 从"如何组织代码"的框架,变成了"如何设计功能"的框架。 这才是组合式 API 真正的威力所在!现在,试着把你项目中的一些功能抽成 Hooks 吧,你会发现代码变得前所未有的清晰和灵活!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐