辰風依恛
文章35
标签0
分类11
Vue3基础复习

Vue3基础复习

Vue3基础复习

Vue3介绍与描述(个人理解)

  • Vue3相比Vue2有更好的性能,更小的打包体积
  • 采用Composition API,提供更好的逻辑复用和代码组织
  • 更好的TypeScript支持
  • 使用Proxy替代Object.defineProperty,响应式系统更强大
  • 支持Tree-shaking,按需引入减少打包体积
  • 新的组件:Fragment、Teleport、Suspense

Vue3基础语法

创建Vue应用

1
2
3
4
5
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

响应式系统

ref和reactive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>{{ count }}</div>
<div>{{ state.name }}</div>
</template>

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

// ref用于基本类型
const count = ref(0)

// reactive用于对象
const state = reactive({
name: 'Vue3',
version: '3.0'
})
</script>

computed计算属性

1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { ref, computed } from 'vue'

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

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

watch和watchEffect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import { ref, watch, watchEffect } from 'vue'

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

// watch监听特定数据源
watch(count, (newValue, oldValue) => {
console.log('count变化:', oldValue, '->', newValue)
})

// watchEffect自动追踪依赖
watchEffect(() => {
console.log('count的值:', count.value)
})
</script>

Composition API

<script setup>语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>{{ title }}</div>
<button @click="increment">{{ count }}</button>
</template>

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

const title = ref('Vue3 Composition API')
const count = ref(0)

const increment = () => {
count.value++
}
</script>

生命周期钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import { onMounted, onUpdated, onUnmounted } from 'vue'

onMounted(() => {
console.log('组件挂载完成')
})

onUpdated(() => {
console.log('组件更新完成')
})

onUnmounted(() => {
console.log('组件卸载完成')
})
</script>

自定义Hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// useCounter.js
import { ref } from 'vue'

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

const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue

return {
count,
increment,
decrement,
reset
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>{{ count }}</div>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">重置</button>
</template>

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

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

组件通信

Props(父传子)

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
<!-- 子组件 Child.vue -->
<template>
<div>{{ message }}</div>
<div>{{ count }}</div>
</template>

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

const props = defineProps({
message: String,
count: Number
})

console.log(props.message)
console.log(props.count)
</script>

<!-- 父组件 Parent.vue -->
<template>
<Child :message="msg" :count="num" />
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const msg = ref('Hello Vue3')
const num = ref(100)
</script>

Emits(子传父)

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
<!-- 子组件 Child.vue -->
<template>
<button @click="sendMessage">发送消息</button>
</template>

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

const emit = defineEmits(['message'])

const sendMessage = () => {
emit('message', '来自子组件的消息')
}
</script>

<!-- 父组件 Parent.vue -->
<template>
<Child @message="handleMessage" />
</template>

<script setup>
import Child from './Child.vue'

const handleMessage = (msg) => {
console.log('收到消息:', msg)
}
</script>

provide/inject(跨层级通信)

1
2
3
4
5
6
7
8
9
10
11
<!-- 祖先组件 -->
<script setup>
import { provide, ref } from 'vue'

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

// 提供数据
provide('theme', theme)
provide('user', user)
</script>
1
2
3
4
5
6
7
8
9
10
11
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'

// 注入数据
const theme = inject('theme')
const user = inject('user')

console.log(theme.value)
console.log(user.value.name)
</script>

插槽

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
<!-- 子组件 -->
<template>
<div class="container">
<header>
<slot name="header">默认头部</slot>
</header>
<main>
<slot>默认内容</slot>
</main>
<footer>
<slot name="footer">默认底部</slot>
</footer>
</div>
</template>

<!-- 父组件 -->
<template>
<ChildComponent>
<template #header>
<h1>自定义头部</h1>
</template>

<p>这是主要内容</p>

<template #footer>
<footer>自定义底部</footer>
</template>
</ChildComponent>
</template>

新组件特性

Fragment(片段)

Vue3支持多个根元素:

1
2
3
4
5
<template>
<header>头部</header>
<main>主体</main>
<footer>底部</footer>
</template>

Teleport(传送)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<button @click="showModal = true">打开模态框</button>

<Teleport to="body">
<div v-if="showModal" class="modal">
<h2>模态框标题</h2>
<button @click="showModal = false">关闭</button>
</div>
</Teleport>
</template>

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

const showModal = ref(false)
</script>

Suspense(异步组件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>

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

const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>

路由:Vue Router 4

基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]

const router = createRouter({
history: createWebHistory(),
routes
})

export default router
1
2
3
4
5
6
7
8
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

路由使用

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
<template>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link to="/dynamic">动态路由</router-link>
</nav>
<router-view />
</template>

<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

const goToAbout = () => {
router.push('/about')
}

// 动态添加路由
const addDynamicRoute = () => {
router.addRoute({
path: '/dynamic',
name: 'Dynamic',
component: () => import('../views/Dynamic.vue')
})

// 跳转到新添加的路由
router.push('/dynamic')
}

// 获取当前路由信息
console.log('当前路由:', route.path)
console.log('路由参数:', route.params)
console.log('查询参数:', route.query)

// 路由导航方法
const navigate = {
// 编程式导航
goToHome: () => router.push('/'),
goToAbout: () => router.push('/about'),

// 带参数导航
goToUser: (id) => router.push({ name: 'User', params: { id } }),

// 带查询参数
goToSearch: (keyword) => router.push({ path: '/search', query: { q: keyword } }),

// 替换当前路由(不记录历史)
replaceHome: () => router.replace('/'),

// 前进后退
goBack: () => router.go(-1),
goForward: () => router.go(1),

// 检查路由是否存在
hasRoute: (name) => router.hasRoute(name),

// 获取路由列表
getRoutes: () => router.getRoutes()
}

// 动态路由操作
const dynamicRoutes = {
// 添加嵌套路由
addNestedRoute: () => {
router.addRoute('Home', {
path: 'settings',
component: () => import('../views/Settings.vue')
})
},

// 删除路由
removeRoute: (name) => {
router.removeRoute(name)
},

// 添加路由守卫
addGuard: () => {
router.beforeEach((to, from, next) => {
console.log(`路由守卫: ${from.path} -> ${to.path}`)
next()
})
}
}
</script>

路由守卫

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
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 检查用户是否登录
if (to.meta.requiresAuth && !isLoggedIn()) {
next('/login')
} else {
next()
}
})

// 全局后置守卫
router.afterEach((to, from) => {
// 页面访问统计
console.log(`从 ${from.path} 跳转到 ${to.path}`)
})

// 路由独享守卫
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
// 检查管理员权限
if (hasAdminPermission()) {
next()
} else {
next('/unauthorized')
}
}
}
]

// 组件内守卫
const UserProfile = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被验证前调用
next(vm => {
// 通过 `vm` 访问组件实例
console.log(vm.user)
})
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
this.userData = fetchUser(to.params.id)
},
beforeRouteLeave(to, from) {
// 在导航离开该组件的对应路由时调用
const answer = window.confirm('确定要离开吗?')
if (!answer) return false
}
}

状态管理:Pinia

安装和配置

1
npm install pinia
1
2
3
4
5
6
7
8
9
10
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')

定义Store

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
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: '计数器'
}),

getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
}
},

actions: {
increment() {
this.count++
},

async incrementAsync() {
// 支持异步操作
await new Promise(resolve => setTimeout(resolve, 1000))
this.count++
},

reset() {
this.count = 0
}
}
})

Composition 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
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('计数器')

const doubleCount = computed(() => count.value * 2)
const doubleCountPlusOne = computed(() => doubleCount.value + 1)

function increment() {
count.value++
}

async function incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
count.value++
}

function reset() {
count.value = 0
}

return {
count,
name,
doubleCount,
doubleCountPlusOne,
increment,
incrementAsync,
reset
}
})

使用Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>{{ counterStore.count }}</div>
<div>{{ counterStore.doubleCount }}</div>
<button @click="counterStore.increment()">+</button>
<button @click="counterStore.reset()">重置</button>
</template>

<script setup>
import { useCounterStore } from '../stores/counter'
import { storeToRefs } from 'pinia'

const counterStore = useCounterStore()

// 响应式解构(保持响应性)
const { count, doubleCount } = storeToRefs(counterStore)
</script>

Store组合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
state: () => ({
user: null,
isLoggedIn: false
}),

actions: {
async login(credentials) {
// 登录逻辑
this.user = { name: '用户', id: 1 }
this.isLoggedIn = true
},

logout() {
this.user = null
this.isLoggedIn = false
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在另一个store中使用
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', {
actions: {
async checkout() {
const userStore = useUserStore()

if (!userStore.isLoggedIn) {
throw new Error('请先登录')
}

// 结账逻辑
}
}
})

Pinia持久化插件 (pinia-plugin-persistedstate)

安装和配置

1
npm install pinia-plugin-persistedstate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

// 使用持久化插件
pinia.use(createPersistedState({
// 全局配置
storage: localStorage, // 默认使用localStorage
key: id => `__persisted__${id}`, // 存储键名格式
beforeRestore: (context) => {
console.log('开始恢复状态:', context.store.$id)
},
afterRestore: (context) => {
console.log('状态恢复完成:', context.store.$id)
}
}))

app.use(pinia)
app.mount('#app')

Store级别的持久化配置

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
// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
state: () => ({
user: null,
isLoggedIn: false,
preferences: {
theme: 'light',
language: 'zh-CN'
}
}),

persist: {
// 启用持久化
enabled: true,

// 自定义存储策略
strategies: [
{
key: 'user-store', // 自定义存储键名
storage: localStorage, // 存储介质
paths: ['user', 'isLoggedIn', 'preferences'], // 指定要持久化的字段
}
]
},

actions: {
async login(credentials) {
this.user = { name: '用户', id: 1 }
this.isLoggedIn = true
},

logout() {
this.user = null
this.isLoggedIn = false
},

updatePreferences(newPrefs) {
this.preferences = { ...this.preferences, ...newPrefs }
}
}
})

组合式Store的持久化

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
// stores/settings.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useSettingsStore = defineStore('settings', () => {
const theme = ref('light')
const language = ref('zh-CN')
const fontSize = ref(14)

const isDark = computed(() => theme.value === 'dark')

function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}

function setLanguage(lang) {
language.value = lang
}

return {
theme,
language,
fontSize,
isDark,
toggleTheme,
setLanguage
}
}, {
// 持久化配置
persist: {
key: 'app-settings',
storage: localStorage,
paths: ['theme', 'language', 'fontSize']
}
})

多种存储策略

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
// stores/cart.js
import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
total: 0,
lastUpdated: null
}),

persist: {
strategies: [
{
// 购物车数据使用sessionStorage,关闭浏览器后清除
key: 'cart-session',
storage: sessionStorage,
paths: ['items', 'total']
},
{
// 最后更新时间使用localStorage长期保存
key: 'cart-last-updated',
storage: localStorage,
paths: ['lastUpdated']
}
]
},

actions: {
addItem(item) {
this.items.push(item)
this.total += item.price
this.lastUpdated = new Date().toISOString()
},

clearCart() {
this.items = []
this.total = 0
this.lastUpdated = new Date().toISOString()
}
}
})

自定义序列化

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
// stores/complex.js
import { defineStore } from 'pinia'

export const useComplexStore = defineStore('complex', {
state: () => ({
// 包含Date对象和Set对象的复杂状态
createdAt: new Date(),
tags: new Set(['vue', 'pinia']),
metadata: new Map([['version', '1.0.0']])
}),

persist: {
serializer: {
// 自定义序列化
serialize: (value) => {
return JSON.stringify(value, (key, value) => {
if (value instanceof Date) {
return { __type: 'Date', value: value.toISOString() }
}
if (value instanceof Set) {
return { __type: 'Set', value: Array.from(value) }
}
if (value instanceof Map) {
return { __type: 'Map', value: Array.from(value.entries()) }
}
return value
})
},

// 自定义反序列化
deserialize: (value) => {
return JSON.parse(value, (key, value) => {
if (value && value.__type === 'Date') {
return new Date(value.value)
}
if (value && value.__type === 'Set') {
return new Set(value.value)
}
if (value && value.__type === 'Map') {
return new Map(value.value)
}
return value
})
}
}
}
})

条件持久化

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
// stores/conditional.js
import { defineStore } from 'pinia'

export const useConditionalStore = defineStore('conditional', {
state: () => ({
publicData: '所有人都能看到',
privateData: '敏感信息',
shouldPersist: true
}),

persist: {
enabled: true,

// 条件持久化
filter: (persistedState) => {
// 只持久化publicData,过滤掉privateData
const { privateData, ...publicState } = persistedState
return publicState
},

// 运行时条件
condition: (store) => {
// 只有当shouldPersist为true时才持久化
return store.shouldPersist
}
}
})

指令和修饰符

自定义指令

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>
<input v-focus />
<div v-color="'red'">红色文字</div>
</template>

<script>
import { directive } from 'vue'

export default {
directives: {
focus: {
mounted(el) {
el.focus()
}
},

color: {
mounted(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
el.style.color = binding.value
}
}
}
}
</script>

全局指令

1
2
3
4
5
6
// main.js
app.directive('focus', {
mounted(el) {
el.focus()
}
})

工具函数

响应式工具

1
2
3
4
5
6
import { isRef, unref, toRef, toRefs } from 'vue'

// isRef: 检查是否为ref对象
// unref: 如果是ref返回其值,否则返回参数本身
// toRef: 为响应式对象的属性创建ref
// toRefs: 将响应式对象转换为普通对象,其中每个属性都是ref

生命周期工具

1
2
3
4
5
import { onMounted, onUpdated, onUnmounted, nextTick } from 'vue'

// nextTick: DOM更新后执行回调
await nextTick()
// DOM已经更新

性能优化

响应式优化

1
2
3
4
5
import { shallowRef, shallowReactive, markRaw } from 'vue'

// shallowRef: 浅层ref,不深度响应
// shallowReactive: 浅层reactive,只响应根级属性
// markRaw: 标记对象永远不会被转为响应式代理

组件优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<HeavyComponent v-if="show" />
</template>

<script>
import { defineAsyncComponent } from 'vue'

// 异步组件
const HeavyComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)

export default {
components: { HeavyComponent },
setup() {
const show = ref(false)

return { show }
}
}
</script>

TypeScript支持

组件类型定义

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
<template>
<div>{{ title }}</div>
<button @click="increment">{{ count }}</button>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
name: 'MyComponent',

props: {
initialCount: {
type: Number,
default: 0
},
title: {
type: String,
required: true
}
},

setup(props) {
const count = ref(props.initialCount)

const increment = () => {
count.value++
}

return {
count,
increment
}
}
})
</script>

Store类型定义

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
// stores/counter.ts
import { defineStore } from 'pinia'

interface CounterState {
count: number
name: string
}

export const useCounterStore = defineStore('counter', {
state: (): CounterState => ({
count: 0,
name: '计数器'
}),

getters: {
doubleCount(state): number {
return state.count * 2
}
},

actions: {
increment(): void {
this.count++
}
}
})

总结

Vue3带来了许多改进和新特性:

  1. 更好的性能:更快的渲染速度,更小的打包体积
  2. Composition API:更好的逻辑复用和代码组织
  3. 更好的TypeScript支持:完整的类型推断
  4. 新的响应式系统:基于Proxy,更强大
  5. 新的内置组件:Fragment、Teleport、Suspense
  6. 改进的工具链:Vite构建工具,开发体验更好

Vue3的学习曲线相对平缓,特别是对于有Vue2经验的开发者。建议从Composition API开始学习,逐步掌握新的特性和最佳实践。

本文作者:辰風依恛
本文链接:https://766187397.github.io/2025/10/12/Vue3%E5%9F%BA%E7%A1%80%E5%A4%8D%E4%B9%A0/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×