组合式API(Composition API)是Vue3引入的新特性,提供了一种更灵活的方式来组织组件逻辑。它解决了Options API在大型组件中逻辑分散的问题。
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是更简洁的语法,自动暴露所有顶层变量。
<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>
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用于创建响应式的对象。
<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>
<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>
<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>
<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>
<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>
<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>
将可复用的逻辑提取到独立的函数中。
// 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 -->
<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>