← 返回主页

第9课: 组合式API

什么是组合式API?

组合式API(Composition API)是Vue3引入的新特性,提供了一种更灵活的方式来组织组件逻辑。它解决了Options API在大型组件中逻辑分散的问题。

setup函数

setup是组合式API的入口点,在组件创建之前执行。

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

<script>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() {
      count.value++
    }

    // 返回的内容可在模板中使用
    return {
      count,
      increment
    }
  }
}
</script>

script setup语法糖

script setup是更简洁的语法,自动暴露所有顶层变量。

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

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>
推荐:使用script setup语法,代码更简洁,性能更好。

响应式基础:ref

ref用于创建响应式的基本类型数据。

<script setup>
import { ref } from 'vue'

// 创建响应式引用
const count = ref(0)
const message = ref('Hello')
const user = ref({ name: '张三' })

// 访问和修改值需要使用.value
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

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

<template>
  <div>{{ count }}</div>
</template>

响应式基础:reactive

reactive用于创建响应式的对象。

<script setup>
import { reactive } from 'vue'

// 创建响应式对象
const state = reactive({
  count: 0,
  message: 'Hello',
  user: {
    name: '张三',
    age: 25
  }
})

// 直接访问和修改属性
console.log(state.count) // 0
state.count++
console.log(state.count) // 1

// 嵌套属性也是响应式的
state.user.age = 26
</script>

<template>
  <div>
    <p>{{ state.count }}</p>
    <p>{{ state.user.name }}</p>
  </div>
</template>

ref vs reactive

<script setup>
import { ref, reactive } from 'vue'

// ref:适合基本类型,需要.value
const count = ref(0)
count.value++

// reactive:适合对象,不需要.value
const state = reactive({ count: 0 })
state.count++

// ref也可以包装对象
const user = ref({ name: '张三' })
user.value.name = '李四'

// reactive不能直接替换整个对象
let state2 = reactive({ count: 0 })
// 错误:会失去响应式
state2 = { count: 1 }
// 正确:修改属性
state2.count = 1
</script>
选择建议:
- 基本类型:使用ref
- 对象类型:ref和reactive都可以,ref更灵活
- 需要替换整个对象:使用ref

computed计算属性

<script setup>
import { ref, computed } from 'vue'

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

// 只读计算属性
const fullName = computed(() => {
  return firstName.value + ' ' + lastName.value
})

// 可写计算属性
const fullNameWritable = computed({
  get() {
    return firstName.value + ' ' + lastName.value
  },
  set(value) {
    const names = value.split(' ')
    firstName.value = names[0]
    lastName.value = names[1]
  }
})

fullNameWritable.value = '李 四'
</script>

watch侦听器

<script setup>
import { ref, watch } from 'vue'

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

// 侦听单个ref
watch(count, (newValue, oldValue) => {
  console.log(`count从${oldValue}变为${newValue}`)
})

// 侦听多个源
watch([count, () => user.value.name], ([newCount, newName]) => {
  console.log(`count: ${newCount}, name: ${newName}`)
})

// 深度侦听
watch(user, (newValue) => {
  console.log('user变化', newValue)
}, { deep: true })

// 立即执行
watch(count, (value) => {
  console.log('count:', value)
}, { immediate: true })
</script>

watchEffect

<script setup>
import { ref, watchEffect } from 'vue'

const count = ref(0)
const message = ref('Hello')

// 自动追踪依赖
watchEffect(() => {
  console.log(`count: ${count.value}, message: ${message.value}`)
})

// 停止侦听
const stop = watchEffect(() => {
  console.log(count.value)
})

// 调用stop停止侦听
stop()
</script>

toRef和toRefs

<script setup>
import { reactive, toRef, toRefs } from 'vue'

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

// toRef:创建单个属性的ref
const name = toRef(state, 'name')
name.value = '李四' // state.name也会变化

// toRefs:将对象的所有属性转为ref
const { age } = toRefs(state)
age.value = 26 // state.age也会变化

// 解构后保持响应式
const { name: userName, age: userAge } = toRefs(state)
</script>

组合式函数(Composables)

将可复用的逻辑提取到独立的函数中。

// useCounter.js
import { ref } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)

  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  function reset() {
    count.value = initialValue
  }

  return {
    count,
    increment,
    decrement,
    reset
  }
}
<!-- 使用组合式函数 -->
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="reset">重置</button>
  </div>
</template>

<script setup>
import { useCounter } from './useCounter'

const { count, increment, decrement, reset } = useCounter(10)
</script>

实战示例:鼠标追踪

// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => {
    window.addEventListener('mousemove', update)
  })

  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })

  return { x, y }
}
<template>
  <div>鼠标位置: {{ x }}, {{ y }}</div>
</template>

<script setup>
import { useMouse } from './useMouse'

const { x, y } = useMouse()
</script>

实战示例:异步数据获取

// useFetch.js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)

  async function fetchData() {
    loading.value = true
    error.value = null

    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }

  fetchData()

  return { data, error, loading, refetch: fetchData }
}
<template>
  <div>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">错误: {{ error.message }}</div>
    <div v-else>
      <pre>{{ data }}</pre>
      <button @click="refetch">重新加载</button>
    </div>
  </div>
</template>

<script setup>
import { useFetch } from './useFetch'

const { data, error, loading, refetch } = useFetch('/api/users')
</script>

Options API vs Composition API

<!-- Options API -->
<script>
export default {
  data() {
    return { count: 0 }
  },
  computed: {
    double() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('mounted')
  }
}
</script>
<!-- Composition API -->
<script setup>
import { ref, computed, onMounted } from 'vue'

const count = ref(0)

const double = computed(() => count.value * 2)

function increment() {
  count.value++
}

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

练习

  1. 创建一个useLocalStorage组合式函数,实现数据持久化
  2. 创建一个useDebounce组合式函数,实现防抖功能
  3. 将一个Options API组件改写为Composition API
  4. 创建一个useForm组合式函数,处理表单验证