目录
- 前言
- Vue3核心新特性概览
- Composition API基础
- 对比Options API与Composition API
- 响应式系统
- 生命周期钩子
- 组合式函数(Composables)
- 实战案例:待办事项应用
- 性能优化最佳实践
- 总结与展望
前言
Vue3的发布标志着这个流行前端框架的重大升级。其中最引人注目的变化是引入了Composition API,它为开发者提供了一种全新的组织组件逻辑的方式。本文将深入探讨Vue3的新特性,特别是Composition API的实际应用,帮助开发者掌握这一强大工具。
Vue3核心新特性概览
Vue3相比Vue2有许多显著的改进:
- 性能提升:重写虚拟DOM实现,渲染速度提升1.3~2倍,内存占用减少一半
- Tree-shaking支持:更好的打包优化,减小应用体积
- Composition API:全新的逻辑组织和复用方式
- Teleport组件:可以将内容渲染到DOM树的任何位置
- Fragments:组件可以有多个根节点
- Suspense:处理异步组件的新方式
- TypeScript支持:更完善的类型推断
- 自定义渲染器API:更容易创建自定义渲染器
Composition API基础
Composition API是Vue3最重要的特性之一,它提供了一种新的组织组件逻辑的方式。
基本结构
<script setup>
import { ref, computed, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log('组件已挂载')
})
</script>
<template>
<div>
<p>计数: {{ count }}</p>
<p>双倍计数: {{ doubleCount }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script setup>
语法
Vue3.2引入的<script setup>
是使用Composition API的推荐方式,它具有以下优势:
- 更少的样板代码
- 变量和函数直接在模板中可用
- 更好的运行时性能
- 更好的IDE类型推断
对比Options API与Composition API
Options API(Vue2的主要方式)
export default {
data() {
return {
count: 0
}
},
computed: {
doubleCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('组件已挂载')
}
}
Composition API(Vue3推荐方式)
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('组件已挂载')
})
return {
count,
doubleCount,
increment
}
}
}
主要区别与优势
- 逻辑组织:Composition API按照逻辑关注点组织代码,而非选项类型
- 逻辑复用:更容易提取和复用逻辑,没有this的困扰
- 类型推断:对TypeScript更友好
- 打包优化:更好的Tree-shaking支持,减小应用体积
响应式系统
Vue3的响应式系统是完全重写的,基于ES6的Proxy:
核心响应式API
- ref:处理基本类型的响应式数据
- reactive:处理对象类型的响应式数据
- computed:创建计算属性
- watch/watchEffect:监听数据变化
import { ref, reactive, computed, watch, watchEffect } from 'vue'
// 基本类型的响应式
const count = ref(0)
console.log(count.value) // 访问值需要.value
// 对象类型的响应式
const state = reactive({
name: '张三',
age: 25
})
console.log(state.name) // 直接访问属性
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 监听特定数据
watch(count, (newValue, oldValue) => {
console.log(`count从${oldValue}变为${newValue}`)
})
// 自动追踪依赖
watchEffect(() => {
console.log(`count: ${count.value}, name: ${state.name}`)
})
ref vs reactive
ref | reactive | |
---|---|---|
适用类型 | 任何类型 | 仅对象类型 |
访问方式 | .value(模板中自动解包) | 直接访问属性 |
解构行为 | 保持响应性 | 失去响应性(需使用toRefs) |
使用场景 | 简单值或需要解构时 | 复杂对象且不需解构时 |
生命周期钩子
Composition API提供了一套新的生命周期钩子:
Options API | Composition API |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
import { onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
onMounted(() => {
console.log('组件已挂载')
})
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
}
}
组合式函数(Composables)
Composition API最强大的特性之一是能够轻松创建和使用可复用的组合式函数(Composables)。
创建一个组合式函数
// useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
return {
count,
doubleCount,
increment,
decrement
}
}
使用组合式函数
<script setup>
import { useCounter } from './composables/useCounter'
const { count, doubleCount, increment, decrement } = useCounter(10)
</script>
<template>
<div>
<p>计数: {{ count }}</p>
<p>双倍计数: {{ doubleCount }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
</div>
</template>
常用组合式函数示例
1. 使用鼠标位置
// 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 }
}
2. 使用本地存储
// useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const value = ref(JSON.parse(localStorage.getItem(key)) || defaultValue)
watch(value, val => {
localStorage.setItem(key, JSON.stringify(val))
})
return value
}
实战案例:待办事项应用
下面是一个使用Composition API构建的简单待办事项应用:
<script setup>
import { ref, computed } from 'vue'
import { useLocalStorage } from './composables/useLocalStorage'
// 使用本地存储保存待办事项
const todos = useLocalStorage('todos', [])
const newTodo = ref('')
// 计算属性:未完成的待办事项数量
const remaining = computed(() => {
return todos.value.filter(todo => !todo.completed).length
})
// 添加待办事项
function addTodo() {
if (newTodo.value.trim()) {
todos.value.push({
id: Date.now(),
text: newTodo.value,
completed: false
})
newTodo.value = ''
}
}
// 删除待办事项
function removeTodo(id) {
todos.value = todos.value.filter(todo => todo.id !== id)
}
// 切换待办事项完成状态
function toggleTodo(id) {
const todo = todos.value.find(todo => todo.id === id)
if (todo) {
todo.completed = !todo.completed
}
}
</script>
<template>
<div class="todo-app">
<h1>待办事项列表</h1>
<!-- 添加待办事项 -->
<div class="add-todo">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="添加新的待办事项"
/>
<button @click="addTodo">添加</button>
</div>
<!-- 待办事项列表 -->
<ul class="todo-list">
<li v-for="todo in todos" :key="todo.id" :class="{ completed: todo.completed }">
<input
type="checkbox"
:checked="todo.completed"
@change="toggleTodo(todo.id)"
/>
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
<!-- 底部状态 -->
<div class="todo-footer" v-if="todos.length">
<span>{{ remaining }} 项待完成</span>
</div>
</div>
</template>
<style scoped>
.todo-app {
max-width: 500px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.add-todo {
display: flex;
margin-bottom: 20px;
}
.add-todo input {
flex-grow: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
}
.add-todo button {
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-list li {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-list li.completed span {
text-decoration: line-through;
color: #999;
}
.todo-list li span {
flex-grow: 1;
margin: 0 15px;
}
.todo-list li button {
background-color: #f44336;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
}
.todo-footer {
margin-top: 20px;
color: #666;
}
</style>
性能优化最佳实践
使用Composition API时,需要注意一些性能优化的最佳实践:
1. 避免不必要的响应式转换
// 不好的做法:创建不需要响应式的大型数据
const data = reactive(hugeData)
// 好的做法:仅对需要响应式的数据使用reactive或ref
const data = hugeData // 普通对象
const reactiveState = reactive({ count: 0 }) // 只对需要响应式的状态使用reactive
2. 使用shallowRef和shallowReactive
对于大型对象,如果只需要跟踪顶层属性的变化:
import { shallowRef, shallowReactive } from 'vue'
// 只有.value的变更是响应式的,而不会深度追踪
const state = shallowRef({ count: 0 })
// 只有顶层属性的变更是响应式的
const user = shallowReactive({
name: '张三',
address: { city: '北京' } // 修改city不会触发更新
})
3. 使用v-once和v-memo
<!-- 只渲染一次 -->
<div v-once>{{ expensiveComputation() }}</div>
<!-- 依赖项变化时才重新渲染 -->
<div v-memo="[item.id]">{{ item.name }}</div>
4. 合理使用计算属性缓存
// 不好的做法:在方法中计算
function getFilteredItems() {
return items.value.filter(item => item.price > 100)
}
// 好的做法:使用计算属性
const filteredItems = computed(() => {
return items.value.filter(item => item.price > 100)
})
总结与展望
Vue3的Composition API为我们提供了一种更灵活、更强大的组织组件逻辑的方式。它解决了Vue2中存在的许多问题,如逻辑复用困难、组件过于臃肿等。虽然学习曲线略陡,但一旦掌握,将极大提升开发效率和代码质量。
随着Vue生态系统的发展,我们可以期待更多基于Composition API的库和工具,以及更多的最佳实践和模式的出现。如果你还在使用Options API,不妨开始尝试Composition API,体验Vue3带来的全新开发体验。
参考资料: