Vue.js 3.0带来了许多令人兴奋的新特性,今天我们来深入了解这些变化以及如何在实际项目中应用它们。
🚀 Vue 3的主要改进
性能提升
- 更快的虚拟DOM:重写了虚拟DOM,性能提升1.3-2倍
- 更小的包体积:通过tree-shaking支持,减少了41%的包体积
- 更好的TypeScript支持:完全用TypeScript重写
新的响应式系统
基于ES6的Proxy实现,相比Vue 2的Object.defineProperty:
- 支持数组索引和length的监听
- 支持Map、Set、WeakMap、WeakSet
- 支持13种数组变更方法的监听
💡 Composition API详解
为什么需要Composition API?
Vue 2的Options API在处理复杂组件时存在一些问题:
基础语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| import { ref, reactive, computed, watch, onMounted } from 'vue'
export default {
setup() {
// 响应式数据
const count = ref(0)
const state = reactive({
name: '张三',
age: 25
})
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
const increment = () => {
count.value++
}
// 生命周期
onMounted(() => {
console.log('组件已挂载')
})
// 监听器
watch(count, (newVal, oldVal) => {
console.log(`count从${oldVal}变为${newVal}`)
})
return {
count,
state,
doubleCount,
increment
}
}
}
|
组合函数(Composables)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| // 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)
return {
count: readonly(count),
increment,
decrement,
reset,
isEven
}
}
// 在组件中使用
import { useCounter } from './composables/useCounter'
export default {
setup() {
const { count, increment, decrement, isEven } = useCounter(10)
return {
count,
increment,
decrement,
isEven
}
}
}
|
逻辑复用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| // composables/useFetch.js
import { ref, reactive } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(true)
const error = ref(null)
const fetchData = async () => {
try {
loading.value = true
error.value = null
const response = await fetch(url)
if (!response.ok) throw new Error('请求失败')
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
fetchData()
return { data, loading, error, refetch: fetchData }
}
// 在组件中使用
export default {
setup() {
const { data: users, loading, error } = useFetch('/api/users')
return { users, loading, error }
}
}
|
⚡ 新特性深入
1. Teleport(传送门)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| <template>
<div class="modal-container">
<button @click="showModal = true">打开模态框</button>
<!-- 将模态框传送到body下 -->
<Teleport to="body">
<div v-if="showModal" class="modal-backdrop">
<div class="modal">
<h3>模态框标题</h3>
<p>这是模态框内容</p>
<button @click="showModal = false">关闭</button>
</div>
</div>
</Teleport>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const showModal = ref(false)
return { showModal }
}
}
</script>
|
2. Fragments(片段)
Vue 3支持组件有多个根节点:
1
2
3
4
5
6
| <template>
<!-- Vue 2中这样写会报错 -->
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
|
3. Suspense(实验性)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <template>
<Suspense>
<!-- 异步组件 -->
<AsyncComponent />
<!-- 加载状态 -->
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
<script>
import { defineAsyncComponent } from 'vue'
export default {
components: {
AsyncComponent: defineAsyncComponent(() => import('./AsyncComponent.vue'))
}
}
</script>
|
🔄 响应式系统升级
ref vs reactive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import { ref, reactive, toRefs } from 'vue'
// ref:用于基本类型
const count = ref(0)
const name = ref('张三')
// reactive:用于对象类型
const state = reactive({
user: {
name: '李四',
age: 30
},
posts: []
})
// toRefs:将reactive对象转为ref
const { user, posts } = toRefs(state)
|
响应式工具函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| import {
readonly,
shallowRef,
shallowReactive,
toRef,
unref,
isRef,
isReactive
} from 'vue'
// 只读
const original = reactive({ count: 0 })
const copy = readonly(original)
// 浅层响应式
const shallowState = shallowReactive({
foo: 1,
nested: { bar: 2 }
})
// 判断类型
console.log(isRef(count)) // true
console.log(isReactive(state)) // true
|
🎨 模板语法增强
v-memo(3.2+新增)
1
2
3
4
5
6
7
| <template>
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">
<!-- 只有当id或selected改变时才重新渲染 -->
<span>{{ item.name }}</span>
<span>{{ item.selected ? '已选择' : '未选择' }}</span>
</div>
</template>
|
CSS中的v-bind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| <template>
<div class="dynamic-color">文本颜色动态改变</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const color = ref('red')
setTimeout(() => {
color.value = 'blue'
}, 2000)
return { color }
}
}
</script>
<style>
.dynamic-color {
color: v-bind(color);
}
</style>
|
🛠️ 开发工具和生态
Vite集成
1
2
3
4
5
| # 创建Vue 3项目
npm create vue@latest my-vue-app
cd my-vue-app
npm install
npm run dev
|
Vue 3需要使用新版本的devtools:
- 支持Composition API调试
- 更好的性能分析
- 时间旅行调试
Pinia状态管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
// 在组件中使用
import { useCounterStore } from '@/stores/counter'
export default {
setup() {
const store = useCounterStore()
return {
count: store.count,
increment: store.increment
}
}
}
|
📚 实战项目示例
待办事项应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
| <template>
<div class="todo-app">
<h1>待办事项</h1>
<form @submit.prevent="addTodo">
<input
v-model="newTodo"
placeholder="添加新任务..."
required
/>
<button type="submit">添加</button>
</form>
<div class="filters">
<button
v-for="filter in filters"
:key="filter"
:class="{ active: currentFilter === filter }"
@click="currentFilter = filter"
>
{{ filter }}
</button>
</div>
<ul class="todo-list">
<li
v-for="todo in filteredTodos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<input
type="checkbox"
v-model="todo.completed"
/>
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
<div class="stats">
<span>总计: {{ todos.length }}</span>
<span>已完成: {{ completedCount }}</span>
<span>未完成: {{ incompleteCount }}</span>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'TodoApp',
setup() {
// 状态
const todos = ref([])
const newTodo = ref('')
const currentFilter = ref('全部')
const filters = ['全部', '进行中', '已完成']
// 计算属性
const filteredTodos = computed(() => {
switch (currentFilter.value) {
case '进行中':
return todos.value.filter(todo => !todo.completed)
case '已完成':
return todos.value.filter(todo => todo.completed)
default:
return todos.value
}
})
const completedCount = computed(() =>
todos.value.filter(todo => todo.completed).length
)
const incompleteCount = computed(() =>
todos.value.filter(todo => !todo.completed).length
)
// 方法
const addTodo = () => {
if (newTodo.value.trim()) {
todos.value.push({
id: Date.now(),
text: newTodo.value.trim(),
completed: false
})
newTodo.value = ''
}
}
const removeTodo = (id) => {
const index = todos.value.findIndex(todo => todo.id === id)
if (index > -1) {
todos.value.splice(index, 1)
}
}
return {
todos,
newTodo,
currentFilter,
filters,
filteredTodos,
completedCount,
incompleteCount,
addTodo,
removeTodo
}
}
}
</script>
|
🔧 迁移指南
从Vue 2迁移到Vue 3
1. 全局API变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Vue 2
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
Vue.component('MyComponent', MyComponent)
new Vue({
render: h => h(App)
}).$mount('#app')
// Vue 3
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.component('MyComponent', MyComponent)
app.mount('#app')
|
2. 组件定义变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // Vue 2
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
// Vue 3 (Options API仍然支持)
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
// Vue 3 (推荐使用Composition API)
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
}
|
🎯 最佳实践
1. 何时使用Composition API
- 逻辑复杂的组件
- 需要逻辑复用的场景
- TypeScript项目
- 大型项目
2. 性能优化技巧
1
2
3
4
5
6
7
8
9
10
| // 使用shallowRef避免深度响应式
const largeObject = shallowRef({ /* 大对象 */ })
// 使用markRaw标记不需要响应式的对象
const nonReactiveObj = markRaw({ /* 普通对象 */ })
// 合理使用v-memo
<template>
<ExpensiveComponent v-memo="[valueA, valueB]" />
</template>
|
📊 总结
Vue 3的主要优势:
| 特性 | Vue 2 | Vue 3 |
|---|
| 性能 | 基准 | 提升1.3-2x |
| 包体积 | 基准 | 减少41% |
| TypeScript | 支持 | 原生支持 |
| 组合性 | Mixins | Composition API |
| 响应式 | Object.defineProperty | Proxy |
Vue 3不仅带来了性能提升,更重要的是提供了更好的开发体验和代码组织方式。虽然学习曲线存在,但掌握后能显著提高开发效率。
你在使用Vue 3的过程中遇到了哪些问题?欢迎在评论区交流!