组合式API详解
setup函数
setup基础
<template>
<div>
<h2>{{ title }}</h2>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
<p>双倍: {{ doubleCount }}</p>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'SetupExample',
// setup函数在组件创建之前执行
setup(props, context) {
console.log('setup执行了')
console.log('props:', props)
console.log('context:', context)
// 响应式数据
const title = ref('Composition API示例')
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
const increment = () => {
count.value++
}
// 返回模板需要的数据和方法
return {
title,
count,
doubleCount,
increment
}
}
}
</script>
setup语法糖
<template>
<div>
<h2>{{ title }}</h2>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
<p>双倍: {{ doubleCount }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 使用<script setup>语法糖,无需手动返回
const title = ref('Setup语法糖示例')
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
</script>
props和emit
<!-- 子组件 ChildComponent.vue -->
<template>
<div>
<h3>{{ title }}</h3>
<p>接收到的消息: {{ message }}</p>
<p>计数: {{ count }}</p>
<button @click="handleIncrement">增加并通知父组件</button>
<button @click="sendMessage">发送消息给父组件</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 定义props
const props = defineProps({
message: {
type: String,
required: true
},
initialCount: {
type: Number,
default: 0
}
})
// 定义emits
const emit = defineEmits(['update:count', 'send-message'])
// 响应式数据
const count = ref(props.initialCount)
// 计算属性
const title = computed(() => `子组件 - ${props.message}`)
// 方法
const handleIncrement = () => {
count.value++
emit('update:count', count.value)
}
const sendMessage = () => {
emit('send-message', `来自子组件的消息: ${Date.now()}`)
}
</script>
<!-- 父组件 -->
<template>
<div>
<h2>父组件</h2>
<p>父组件计数: {{ parentCount }}</p>
<p>收到的消息: {{ receivedMessage }}</p>
<ChildComponent
:message="parentMessage"
:initial-count="parentCount"
@update:count="handleCountUpdate"
@send-message="handleMessage"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const parentCount = ref(10)
const parentMessage = ref('Hello from parent')
const receivedMessage = ref('')
const handleCountUpdate = (newCount) => {
parentCount.value = newCount
}
const handleMessage = (message) => {
receivedMessage.value = message
}
</script>
响应式API详解
ref深入
<template>
<div>
<h2>ref详解</h2>
<!-- 基本类型 -->
<div>
<p>字符串: {{ str }}</p>
<p>数字: {{ num }}</p>
<p>布尔值: {{ bool }}</p>
<button @click="updateBasicTypes">更新基本类型</button>
</div>
<!-- 对象类型 -->
<div>
<p>用户: {{ user.name }} - {{ user.age }}岁</p>
<button @click="updateUser">更新用户</button>
</div>
<!-- 数组类型 -->
<div>
<p>列表: {{ list.join(', ') }}</p>
<button @click="updateList">更新列表</button>
</div>
<!-- DOM引用 -->
<div>
<input ref="inputRef" placeholder="输入内容">
<button @click="focusInput">聚焦输入框</button>
</div>
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
// 基本类型的ref
const str = ref('Hello')
const num = ref(42)
const bool = ref(true)
// 对象类型的ref
const user = ref({
name: '张三',
age: 25
})
// 数组类型的ref
const list = ref([1, 2, 3])
// DOM引用
const inputRef = ref(null)
// 更新基本类型
const updateBasicTypes = () => {
str.value = 'Hello Vue3!'
num.value = Math.random() * 100
bool.value = !bool.value
}
// 更新对象
const updateUser = () => {
// 直接替换整个对象
user.value = {
name: '李四',
age: 30
}
// 或者修改对象属性
// user.value.name = '李四'
// user.value.age = 30
}
// 更新数组
const updateList = () => {
list.value.push(list.value.length + 1)
}
// 聚焦输入框
const focusInput = async () => {
await nextTick()
inputRef.value?.focus()
}
</script>
reactive深入
<template>
<div>
<h2>reactive详解</h2>
<!-- 对象响应式 -->
<div>
<h3>用户信息</h3>
<p>姓名: {{ state.user.name }}</p>
<p>年龄: {{ state.user.age }}</p>
<p>邮箱: {{ state.user.email }}</p>
<button @click="updateUser">更新用户</button>
</div>
<!-- 数组响应式 -->
<div>
<h3>任务列表</h3>
<ul>
<li v-for="task in state.tasks" :key="task.id">
{{ task.title }} - {{ task.completed ? '已完成' : '未完成' }}
<button @click="toggleTask(task.id)">切换状态</button>
</li>
</ul>
<button @click="addTask">添加任务</button>
</div>
<!-- 嵌套对象 -->
<div>
<h3>设置</h3>
<p>主题: {{ state.settings.theme }}</p>
<p>语言: {{ state.settings.language }}</p>
<p>通知: {{ state.settings.notifications.email ? '开启' : '关闭' }}</p>
<button @click="updateSettings">更新设置</button>
</div>
</div>
</template>
<script setup>
import { reactive } from 'vue'
// 创建响应式对象
const state = reactive({
user: {
name: '张三',
age: 25,
email: 'zhangsan@example.com'
},
tasks: [
{ id: 1, title: '学习Vue3', completed: false },
{ id: 2, title: '写代码', completed: true }
],
settings: {
theme: 'dark',
language: 'zh-CN',
notifications: {
email: true,
push: false
}
}
})
// 更新用户信息
const updateUser = () => {
state.user.name = '李四'
state.user.age = 30
state.user.email = 'lisi@example.com'
}
// 切换任务状态
const toggleTask = (id) => {
const task = state.tasks.find(t => t.id === id)
if (task) {
task.completed = !task.completed
}
}
// 添加任务
const addTask = () => {
const newId = state.tasks.length + 1
state.tasks.push({
id: newId,
title: `新任务${newId}`,
completed: false
})
}
// 更新设置
const updateSettings = () => {
state.settings.theme = state.settings.theme === 'dark' ? 'light' : 'dark'
state.settings.language = state.settings.language === 'zh-CN' ? 'en-US' : 'zh-CN'
state.settings.notifications.email = !state.settings.notifications.email
}
</script>
toRef和toRefs
<template>
<div>
<h2>toRef和toRefs示例</h2>
<div>
<h3>原始对象</h3>
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
<p>城市: {{ user.city }}</p>
</div>
<div>
<h3>使用toRef</h3>
<p>姓名: {{ userName }}</p>
<button @click="updateUserName">更新姓名</button>
</div>
<div>
<h3>使用toRefs</h3>
<p>年龄: {{ age }}</p>
<p>城市: {{ city }}</p>
<button @click="updateAgeAndCity">更新年龄和城市</button>
</div>
</div>
</template>
<script setup>
import { reactive, toRef, toRefs } from 'vue'
// 响应式对象
const user = reactive({
name: '张三',
age: 25,
city: '北京'
})
// 使用toRef创建单个属性的ref
const userName = toRef(user, 'name')
// 使用toRefs解构响应式对象
const { age, city } = toRefs(user)
// 更新姓名
const updateUserName = () => {
userName.value = '李四'
// 这会同时更新user.name
}
// 更新年龄和城市
const updateAgeAndCity = () => {
age.value = 30
city.value = '上海'
// 这会同时更新user.age和user.city
}
</script>
生命周期钩子
生命周期对比
<template>
<div>
<h2>生命周期示例</h2>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
<p>消息: {{ message }}</p>
</div>
</template>
<script setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
const count = ref(0)
const message = ref('组件已创建')
// 组件挂载前
onBeforeMount(() => {
console.log('onBeforeMount: 组件挂载前')
message.value = '组件即将挂载'
})
// 组件挂载后
onMounted(() => {
console.log('onMounted: 组件挂载后')
message.value = '组件已挂载'
// 可以访问DOM
console.log('DOM元素:', document.querySelector('h2'))
})
// 组件更新前
onBeforeUpdate(() => {
console.log('onBeforeUpdate: 组件更新前')
console.log('更新前的count:', count.value)
})
// 组件更新后
onUpdated(() => {
console.log('onUpdated: 组件更新后')
console.log('更新后的count:', count.value)
})
// 组件卸载前
onBeforeUnmount(() => {
console.log('onBeforeUnmount: 组件卸载前')
// 清理工作,如取消定时器、移除事件监听器等
})
// 组件卸载后
onUnmounted(() => {
console.log('onUnmounted: 组件卸载后')
})
const increment = () => {
count.value++
}
// setup函数相当于beforeCreate和created的组合
console.log('setup: 组件创建时')
</script>
生命周期实际应用
<template>
<div>
<h2>生命周期实际应用</h2>
<div>
<h3>定时器示例</h3>
<p>时间: {{ currentTime }}</p>
<button @click="toggleTimer">{{ timerRunning ? '停止' : '开始' }}定时器</button>
</div>
<div>
<h3>窗口大小监听</h3>
<p>窗口大小: {{ windowSize.width }} x {{ windowSize.height }}</p>
</div>
<div>
<h3>数据获取</h3>
<div v-if="loading">加载中...</div>
<div v-else-if="error">错误: {{ error }}</div>
<div v-else>
<p>用户数量: {{ userData.length }}</p>
<ul>
<li v-for="user in userData" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
<button @click="fetchData">重新获取数据</button>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
// 定时器相关
const currentTime = ref(new Date().toLocaleTimeString())
const timerRunning = ref(false)
let timer = null
// 窗口大小监听
const windowSize = reactive({
width: window.innerWidth,
height: window.innerHeight
})
// 数据获取相关
const userData = ref([])
const loading = ref(false)
const error = ref(null)
// 定时器控制
const toggleTimer = () => {
if (timerRunning.value) {
clearInterval(timer)
timer = null
timerRunning.value = false
} else {
timer = setInterval(() => {
currentTime.value = new Date().toLocaleTimeString()
}, 1000)
timerRunning.value = true
}
}
// 窗口大小变化处理
const handleResize = () => {
windowSize.width = window.innerWidth
windowSize.height = window.innerHeight
}
// 模拟数据获取
const fetchData = async () => {
loading.value = true
error.value = null
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000))
// 模拟数据
userData.value = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
]
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 组件挂载后
onMounted(() => {
// 添加窗口大小监听
window.addEventListener('resize', handleResize)
// 获取初始数据
fetchData()
// 启动定时器
toggleTimer()
})
// 组件卸载前清理
onUnmounted(() => {
// 清理定时器
if (timer) {
clearInterval(timer)
}
// 移除事件监听器
window.removeEventListener('resize', handleResize)
})
</script>
watch和watchEffect
watch监听器
<template>
<div>
<h2>watch监听器示例</h2>
<div>
<h3>基本监听</h3>
<input v-model="searchText" placeholder="输入搜索内容">
<p>搜索结果数量: {{ searchResults.length }}</p>
</div>
<div>
<h3>对象监听</h3>
<input v-model="user.name" placeholder="姓名">
<input v-model.number="user.age" placeholder="年龄" type="number">
<p>用户信息变化次数: {{ userChangeCount }}</p>
</div>
<div>
<h3>多个值监听</h3>
<input v-model="firstName" placeholder="名">
<input v-model="lastName" placeholder="姓">
<p>全名: {{ fullName }}</p>
</div>
</div>
</template>
<script setup>
import { ref, reactive, watch, computed } from 'vue'
// 基本监听
const searchText = ref('')
const searchResults = ref([])
// 监听搜索文本变化
watch(searchText, async (newValue, oldValue) => {
console.log(`搜索文本从 "${oldValue}" 变为 "${newValue}"`)
if (newValue) {
// 模拟搜索API调用
await new Promise(resolve => setTimeout(resolve, 300))
searchResults.value = Array.from({ length: newValue.length }, (_, i) => ({
id: i + 1,
title: `结果 ${i + 1} - ${newValue}`
}))
} else {
searchResults.value = []
}
}, {
// 立即执行
immediate: true
})
// 对象监听
const user = reactive({
name: '张三',
age: 25
})
const userChangeCount = ref(0)
// 深度监听对象
watch(user, (newValue, oldValue) => {
console.log('用户信息变化:', newValue)
userChangeCount.value++
}, {
deep: true
})
// 监听对象的特定属性
watch(() => user.name, (newName, oldName) => {
console.log(`用户名从 "${oldName}" 变为 "${newName}"`)
})
// 多个值监听
const firstName = ref('三')
const lastName = ref('张')
const fullName = ref('')
// 监听多个响应式引用
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log('姓名变化:', { newFirst, newLast }, { oldFirst, oldLast })
fullName.value = `${newLast}${newFirst}`
}, {
immediate: true
})
</script>
watchEffect
<template>
<div>
<h2>watchEffect示例</h2>
<div>
<h3>自动依赖追踪</h3>
<input v-model="keyword" placeholder="关键词">
<select v-model="category">
<option value="">所有分类</option>
<option value="tech">技术</option>
<option value="life">生活</option>
</select>
<p>过滤后的文章数量: {{ filteredArticles.length }}</p>
</div>
<div>
<h3>副作用清理</h3>
<p>鼠标位置: {{ mousePosition.x }}, {{ mousePosition.y }}</p>
<button @click="toggleMouseTracking">
{{ trackingMouse ? '停止' : '开始' }}追踪鼠标
</button>
</div>
</div>
</template>
<script setup>
import { ref, reactive, watchEffect } from 'vue'
// 自动依赖追踪示例
const keyword = ref('')
const category = ref('')
const filteredArticles = ref([])
const articles = [
{ id: 1, title: 'Vue3教程', category: 'tech' },
{ id: 2, title: '生活小贴士', category: 'life' },
{ id: 3, title: 'JavaScript进阶', category: 'tech' },
{ id: 4, title: '健康饮食', category: 'life' }
]
// watchEffect会自动追踪依赖
watchEffect(() => {
console.log('过滤条件变化,重新过滤文章')
filteredArticles.value = articles.filter(article => {
const matchKeyword = !keyword.value ||
article.title.toLowerCase().includes(keyword.value.toLowerCase())
const matchCategory = !category.value || article.category === category.value
return matchKeyword && matchCategory
})
})
// 副作用清理示例
const mousePosition = reactive({ x: 0, y: 0 })
const trackingMouse = ref(false)
const handleMouseMove = (event) => {
mousePosition.x = event.clientX
mousePosition.y = event.clientY
}
// 带清理的watchEffect
watchEffect((onInvalidate) => {
if (trackingMouse.value) {
console.log('开始追踪鼠标')
window.addEventListener('mousemove', handleMouseMove)
// 清理函数
onInvalidate(() => {
console.log('停止追踪鼠标')
window.removeEventListener('mousemove', handleMouseMove)
})
}
})
const toggleMouseTracking = () => {
trackingMouse.value = !trackingMouse.value
}
</script>
逻辑复用
组合函数(Composables)
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
const isEven = computed(() => count.value % 2 === 0)
const isPositive = computed(() => count.value > 0)
return {
count,
increment,
decrement,
reset,
isEven,
isPositive
}
}
// composables/useFetch.js
import { ref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url.value || url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 如果url是响应式的,监听变化
if (typeof url === 'object' && url.value !== undefined) {
watchEffect(fetchData)
} else {
fetchData()
}
return {
data,
loading,
error,
refetch: fetchData
}
}
<!-- 使用组合函数 -->
<template>
<div>
<h2>组合函数示例</h2>
<div>
<h3>计数器组合函数</h3>
<p>计数: {{ count }}</p>
<p>是偶数: {{ isEven ? '是' : '否' }}</p>
<p>是正数: {{ isPositive ? '是' : '否' }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<button @click="reset">重置</button>
</div>
<div>
<h3>数据获取组合函数</h3>
<div v-if="loading">加载中...</div>
<div v-else-if="error">错误: {{ error }}</div>
<div v-else-if="data">
<p>获取到 {{ data.length }} 条数据</p>
<button @click="refetch">重新获取</button>
</div>
</div>
</div>
</template>
<script setup>
import { useCounter } from '@/composables/useCounter'
import { useFetch } from '@/composables/useFetch'
// 使用计数器组合函数
const {
count,
increment,
decrement,
reset,
isEven,
isPositive
} = useCounter(10)
// 使用数据获取组合函数
const {
data,
loading,
error,
refetch
} = useFetch('https://jsonplaceholder.typicode.com/posts')
</script>
总结
本课我们深入学习了Vue3的Composition API:
- setup函数:组件的入口点,替代Options API
- 响应式API:ref、reactive、toRef、toRefs的使用
- 生命周期钩子:在Composition API中的使用方式
- watch和watchEffect:监听响应式数据变化
- 逻辑复用:通过组合函数实现代码复用
下一课预告
在下一课中,我们将学习Vue3的组件开发,包括:
- 组件通信
- 插槽系统
- 动态组件
- 异步组件
💡 小贴士:Composition API的核心思想是按逻辑功能组织代码,而不是按选项类型。这使得代码更容易理解和维护,特别是在大型组件中。
📚 文章对你有帮助?请关注我的公众号,万分感谢!
获取更多优质技术文章,第一时间掌握最新技术动态

关注公众号
第一时间获取最新技术文章

添加微信
技术交流 · 问题答疑 · 学习指导
评论讨论
欢迎留下你的想法和建议