Vue 3 Composition API 深度解析

引言

Vue 3 的 Composition API 是 Vue 框架最具革命性的更新之一。它解决了 Options API 在大型项目中代码组织、逻辑复用和类型推导等方面的痛点。本文将深入讲解 Composition API 的核心概念、使用技巧以及最佳实践。

什么是 Composition API?

Composition API 是一组基于函数的 API,允许我们将相关逻辑组织在一起,而不是将代码分散在 datamethodscomputed 等选项中。

对比:Options API vs Composition API

Options API (Vue 2 风格):

javascript 复制代码
export default {
  data() {
    return {
      count: 0,
      todos: []
    }
  },
  methods: {
    increment() {
      this.count++
    },
    fetchTodos() {
      // 获取待办事项
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  mounted() {
    this.fetchTodos()
  }
}

Composition API (Vue 3 风格):

javascript 复制代码
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const todos = ref([])
    
    const doubleCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
    }
    
    const fetchTodos = async () => {
      // 获取待办事项
    }
    
    onMounted(() => {
      fetchTodos()
    })
    
    return {
      count,
      todos,
      doubleCount,
      increment,
      fetchTodos
    }
  }
}

核心 API 详解

1. ref() - 响应式基础类型

ref() 用于创建响应式的基本类型值。

csharp 复制代码
import { ref } from 'vue'

const count = ref(0)

// 访问和修改
console.log(count.value) // 0
count.value = 1

// 在模板中自动解包,不需要 .value

关键点:

  • 在 JavaScript 中需要通过 .value 访问
  • 在模板中自动解包
  • 支持任意类型的值

2. reactive() - 响应式对象

reactive() 用于创建响应式对象。

php 复制代码
import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: '张三',
    age: 25
  }
})

// 直接访问,不需要 .value
state.count++
state.user.age = 26

ref vs reactive

  • ref 可以处理任意类型,包括基本类型
  • reactive 只能处理对象类型
  • ref 需要 .valuereactive 直接访问

3. computed() - 计算属性

javascript 复制代码
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

const fullName = computed(() => {
  return firstName.value + lastName.value
})

// 只读计算属性
console.log(fullName.value) // '张三'

// 可写计算属性
const fullNameWritable = computed({
  get: () => firstName.value + lastName.value,
  set: (val) => {
    const [first, last] = val.split(' ')
    firstName.value = first
    lastName.value = last
  }
})

4. watch() 和 watchEffect()

watch() - 精确监听:

javascript 复制代码
import { ref, watch } from 'vue'

const count = ref(0)

// 监听单个值
watch(count, (newVal, oldVal) => {
  console.log(`从 ${oldVal} 变为 ${newVal}`)
})

// 监听多个值
watch([count, firstName], ([newCount, newName]) => {
  console.log('变化:', newCount, newName)
})

// 监听对象属性(深度监听)
const user = reactive({ profile: { name: '张三' } })
watch(
  () => user.profile,
  (newVal) => {
    console.log('profile 变化', newVal)
  },
  { deep: true }
)

watchEffect() - 自动追踪依赖:

javascript 复制代码
import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(() => {
  // 自动追踪 count 的变化
  console.log('count 是:', count.value)
  // 这里不需要指定监听谁,Vue 自动分析依赖
})

5. 生命周期钩子

Composition API 中的生命周期钩子需要在 setup() 中调用:

javascript 复制代码
import { 
  onMounted, 
  onUpdated, 
  onUnmounted,
  onBeforeMount,
  onBeforeUpdate,
  onBeforeUnmount,
  onErrorCaptured
} from 'vue'

export default {
  setup() {
    onMounted(() => {
      console.log('组件已挂载')
    })
    
    onUpdated(() => {
      console.log('组件已更新')
    })
    
    onUnmounted(() => {
      console.log('组件已卸载')
    })
    
    // 错误捕获
    onErrorCaptured((err, instance, info) => {
      console.error('捕获到错误:', err)
      return false // 阻止错误继续传播
    })
  }
}

逻辑复用:组合式函数

Composition API 最大的优势在于逻辑复用。我们可以将相关逻辑封装成组合式函数(Composables)。

示例:useCounter

javascript 复制代码
// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const double = computed(() => count.value * 2)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  return {
    count,
    double,
    increment,
    decrement,
    reset
  }
}

使用:

javascript 复制代码
import { useCounter } from './composables/useCounter'

export default {
  setup() {
    const { count, double, increment, decrement } = useCounter(10)
    
    return {
      count,
      double,
      increment,
      decrement
    }
  }
}

示例:useFetch

javascript 复制代码
// composables/useFetch.js
import { ref, onMounted } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const loading = ref(true)
  const error = ref(null)
  
  const fetchData = async () => {
    try {
      loading.value = true
      const response = await fetch(url)
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  onMounted(() => {
    fetchData()
  })
  
  return {
    data,
    loading,
    error,
    refresh: fetchData
  }
}

使用:

javascript 复制代码
import { useFetch } from './composables/useFetch'

export default {
  setup() {
    const { data, loading, error, refresh } = useFetch('/api/users')
    
    return {
      data,
      loading,
      error,
      refresh
    }
  }
}

实际应用场景

1. 表单处理

javascript 复制代码
// composables/useForm.js
import { ref, reactive } from 'vue'

export function useForm(initialValues = {}, validators = {}) {
  const form = reactive({ ...initialValues })
  const errors = reactive({})
  const touched = reactive({})
  
  const validate = (field) => {
    if (validators[field]) {
      const error = validators[field](form[field])
      errors[field] = error
      return !error
    }
    return true
  }
  
  const validateAll = () => {
    return Object.keys(validators).every(validate)
  }
  
  const setField = (field, value) => {
    form[field] = value
    touched[field] = true
  }
  
  return {
    form,
    errors,
    touched,
    validate,
    validateAll,
    setField
  }
}

2. 响应式路由

javascript 复制代码
// composables/useRoute.js
import { ref, computed } from 'vue'
import { useRoute as useVueRoute } from 'vue-router'

export function useRoute() {
  const route = useVueRoute()
  
  const queryParams = computed(() => route.query)
  const params = computed(() => route.params)
  const fullPath = computed(() => route.fullPath)
  
  return {
    route,
    queryParams,
    params,
    fullPath
  }
}

最佳实践

1. 逻辑组织

将相关的逻辑组织在一起:

javascript 复制代码
export default {
  setup() {
    // 状态定义
    const count = ref(0)
    const loading = ref(false)
    
    // 计算属性
    const doubleCount = computed(() => count.value * 2)
    
    // 方法
    const increment = () => count.value++
    
    // 生命周期
    onMounted(() => {
      console.log('mounted')
    })
    
    // 返回
    return {
      count,
      doubleCount,
      increment
    }
  }
}

2. 使用 <script setup>

Vue 3.2+ 引入了 <script setup>,让 Composition API 更加简洁:

xml 复制代码
<script setup>
import { ref, computed, onMounted } from 'vue'

const count = ref(0)
const doubleCount = computed(() => count.value * 2)

const increment = () => count.value++

onMounted(() => {
  console.log('mounted')
})
</script>

<template>
  <div>
    <p>{{ count }}</p>
    <p>{{ doubleCount }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

3. 类型推导

Composition API 对 TypeScript 支持更好:

csharp 复制代码
import { ref } from 'vue'

// 类型自动推导
const count = ref(0) // Ref<number>
const user = ref({ name: '张三', age: 25 }) // Ref<{ name: string, age: number }>

// 显式类型
const count2 = ref<number>(0)
interface User {
  name: string
  age: number
}
const user2 = ref<User>({ name: '张三', age: 25 })

总结

Composition API 为 Vue 3 带来了:

  1. 更好的逻辑组织 - 相关代码放在一起,而不是分散在多个选项中
  2. 更强的逻辑复用 - 通过组合式函数实现代码复用
  3. 更好的 TypeScript 支持 - 类型推导更准确
  4. 更小的打包体积 - 更好的 tree-shaking

虽然学习曲线稍陡,但对于中大型项目来说,Composition API 是更好的选择。建议从简单的 refcomputedwatch 开始,逐步掌握组合式函数的编写技巧。

相关推荐
鹏程十八少1 小时前
11. 2026金三银四 能答对这 29 道题,你的 Android 插件化就算真正通关了
前端·后端·面试
潇凝子潇2 小时前
使用英伟达免费调用多家大模型API
java·前端·javascript
旷世奇才李先生2 小时前
Vue 3\+Vite\+Pinia实战:前端工程化与组件化开发全指南
前端·vue.js
Beginner x_u2 小时前
前端八股整理(手写 01)|Promise 超时控制、红绿灯与 Promise.all
前端·javascript·promise
周bro2 小时前
vue2+element ui 中的el-table表格 选中当前行当前行变色,单选/多选--------续集:表格样式修改整合
vue.js·ui·elementui
万少11 小时前
Vibe Coding不停歇,移动端 TRAE SOLO 让你用手机也能编程啦
前端·javascript·后端
kyriewen1112 小时前
WebAssembly:前端界的“外挂”,让C++代码在浏览器里跑起来
开发语言·前端·javascript·c++·单元测试·ecmascript
烛衔溟13 小时前
TypeScript 接口的基本使用 —— 定义对象形状
前端·javascript·typescript
铁皮饭盒13 小时前
成为AI全栈 - 第3课:路由 RESTful Elysia 状态码 设计规范
前端·后端·全栈