Skip to content

Vue 组合式API

本文介绍Vue 3引入的组合式API(Composition API),包括其设计理念、核心功能以及与选项式API的对比,帮助开发者更好地组织和复用组件逻辑。

什么是组合式API?

组合式API是Vue 3提供的一套新的API,旨在解决大型组件中的逻辑组织和复用问题。与选项式API(Options API)将代码按照data、methods、computed等选项进行组织不同,组合式API允许开发者根据逻辑功能将代码组织成函数,提高代码的可维护性和复用性。

组合式API的优势

  • 更好的代码组织:相关逻辑可以被组合在一起,而不是分散在不同的选项中
  • 更灵活的逻辑复用:通过自定义组合函数(Composables)复用逻辑,替代了选项式API中的mixin
  • 更好的类型支持:与TypeScript结合更紧密,提供更好的类型推断
  • 更小的生产包体积:通过tree-shaking减少未使用代码

setup函数

setup函数是组合式API的入口点,在组件创建之前执行,用于设置组件的状态、方法等。

基本用法

javascript
import { ref } from 'vue'

export default {
  setup() {
    // 声明响应式状态
    const count = ref(0)

    // 声明方法
    const increment = () => {
      count.value++
    }

    // 返回值会暴露给模板和组件实例
    return {
      count,
      increment
    }
  }
}

在模板中使用

html
<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

setup的参数

setup函数接收两个参数:props和context。

javascript
export default {
  props: {
    title: {
      type: String,
      required: true
    }
  },
  setup(props, context) {
    // props是响应式的,不能直接解构
    console.log(props.title)

    // context是一个普通对象,可以解构
    const { attrs, slots, emit, expose } = context

    // 向父组件暴露方法
    expose({
      someMethod() {
        /* ... */
      }
    })

    return {}
  }
}

响应式API

组合式API提供了一系列创建响应式数据的函数,替代了选项式API中的data选项。

ref

ref用于创建可以持有任何类型值的响应式ref对象。

javascript
import { ref } from 'vue'

// 创建ref
const count = ref(0)
const message = ref('Hello')
const isActive = ref(true)

// 访问ref的值
console.log(count.value) // 0

// 修改ref的值
count.value++
console.log(count.value) // 1

reactive

reactive用于创建对象类型的响应式数据。

javascript
import { reactive } from 'vue'

// 创建响应式对象
const user = reactive({
  name: 'John',
  age: 30
})

// 访问和修改属性
console.log(user.name) // John
user.age = 31

响应式转换规则

  • ref用于基本类型数据(String、Number、Boolean等)
  • reactive用于对象类型数据(Object、Array等)
  • ref对象在模板中使用时会自动解包,不需要使用.value
  • 可以使用toRefs将reactive对象转换为多个ref
javascript
import { reactive, toRefs } from 'vue'

const user = reactive({
  name: 'John',
  age: 30
})

// 将reactive对象转换为ref
const { name, age } = toRefs(user)

// 现在可以单独使用这些ref
console.log(name.value) // John

计算属性与侦听器

computed

computed函数用于创建计算属性,与选项式API中的computed选项功能相同。

javascript
import { ref, computed } from 'vue'

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

console.log(doubleCount.value) // 0
count.value = 1
console.log(doubleCount.value) // 2

watch

watch函数用于侦听数据变化,与选项式API中的watch选项功能相同。

javascript
import { ref, watch } from 'vue'

const count = ref(0)

// 侦听ref
watch(count, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`)
})

// 侦听多个源
watch([count, () => user.age], ([newCount, newAge], [oldCount, oldAge]) => {
  /* ... */
})

// 深度侦听
watch(
  () => user,
  (newUser, oldUser) => {
    /* ... */
  },
  { deep: true }
)

watchEffect

watchEffect是一个简化版的watch,它会自动收集依赖。

javascript
import { ref, watchEffect } from 'vue'

const count = ref(0)

const stop = watchEffect(() => {
  console.log(`count is ${count.value}`)
})

// 停止侦听
stop()

生命周期钩子

组合式API提供了一系列生命周期钩子函数,以函数调用的方式使用,替代了选项式API中的生命周期选项。

javascript
import { onMounted, onUpdated, onUnmounted } from 'vue'

export default {
  setup() {
    onMounted(() => {
      console.log('Component mounted!')
    })

    onUpdated(() => {
      console.log('Component updated!')
    })

    onUnmounted(() => {
      console.log('Component unmounted!')
    })
  }
}

常用生命周期钩子

  • onBeforeMount:组件挂载前调用
  • onMounted:组件挂载后调用
  • onBeforeUpdate:组件更新前调用
  • onUpdated:组件更新后调用
  • onBeforeUnmount:组件卸载前调用
  • onUnmounted:组件卸载后调用
  • onErrorCaptured:捕获子组件错误时调用
  • onActivated:keep-alive组件激活时调用
  • onDeactivated:keep-alive组件停用时调用

依赖注入

provide和inject函数用于组件间的依赖注入,替代了选项式API中的provide和inject选项。

父组件提供数据

javascript
import { provide, ref } from 'vue'

export default {
  setup() {
    const theme = ref('light')
    const changeTheme = () => {
      theme.value = theme.value === 'light' ? 'dark' : 'light'
    }

    // 提供数据和方法
    provide('theme', theme)
    provide('changeTheme', changeTheme)
  }
}

子组件注入数据

javascript
import { inject } from 'vue'

export default {
  setup() {
    // 注入数据和方法
    const theme = inject('theme')
    const changeTheme = inject('changeTheme')

    return {
      theme,
      changeTheme
    }
  }
}

组合函数(Composables)

组合函数是组合式API的核心思想,用于封装和复用组件逻辑。组合函数通常命名以use开头。

创建组合函数

javascript
// useCounter.js
import { ref, computed } from 'vue'

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

  return {
    count,
    doubleCount,
    increment,
    decrement,
    reset
  }
}

使用组合函数

javascript
// CounterComponent.vue
import { useCounter } from './useCounter'

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

    return {
      count,
      increment,
      decrement
    }
  }
}

与选项式API的对比

选项式API的局限性

  • 逻辑关注点分散:相关逻辑分散在不同选项中
  • 逻辑复用困难:mixin存在命名冲突、来源不清晰等问题
  • 类型支持有限:对TypeScript支持不够友好

组合式API的改进

  • 逻辑聚合:相关逻辑可以组织在一起
  • 明确的逻辑复用:通过组合函数显式复用逻辑
  • 更好的类型支持:与TypeScript无缝集成
  • 更灵活的代码组织:可以根据功能拆分代码

如何选择

  • 小型组件:选项式API可能更简洁
  • 大型组件:组合式API更有利于维护
  • 逻辑复用需求高:组合式API的组合函数更适合
  • 新项目:推荐使用组合式API
  • 现有项目:可以渐进式采用组合式API

组合式API的最佳实践

提取组合函数

将组件中的逻辑提取为独立的组合函数,提高复用性。

按功能组织代码

将相关的状态和方法组织在一起,而不是按选项类型组织。

使用TypeScript

组合式API与TypeScript结合使用,可以获得更好的类型检查和自动补全。

避免过度拆分

不要为了拆分而拆分,适度的逻辑聚合更有利于维护。

使用命名规范

组合函数以use开头,如useCounter、useApi等,便于识别。

总结

组合式API为Vue开发者提供了一种新的代码组织方式,特别适合处理复杂组件中的逻辑。通过setup函数、响应式API、生命周期钩子和组合函数,开发者可以编写出更具可维护性和复用性的代码。组合式API不是对选项式API的替代,而是提供了一种新的选择,开发者可以根据项目需求和个人偏好选择合适的API风格。