Vue3 完整学习笔记 - 第四部分
Vue3 完整学习笔记 - 第四部分
4. Pinia 状态管理与组件通信
4.1 Pinia 基础
重点掌握:
- Store 的创建和使用
- State、Getters、Actions 的定义
- 组合式风格的 Store
基础 Store 示例:
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 状态
state: () => ({
count: 0,
name: 'Eduardo',
items: []
}),
// 计算属性
getters: {
doubleCount: (state) => state.count * 2,
// 使用this访问其他getter
doubleCountPlusOne(): number {
return this.doubleCount + 1
}
},
// 方法
actions: {
increment() {
this.count++
},
async fetchItems() {
const response = await fetch('/api/items')
this.items = await response.json()
}
}
})
// 组合式写法
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, name, doubleCount, increment }
})
在组件中使用:
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment">+1</button>
<button @click="increment">+1 (Destructured)</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
// 解构时保持响应性
const { count, doubleCount } = storeToRefs(counter)
const { increment } = counter
// 批量修改状态
function updateState() {
counter.$patch({
count: counter.count + 1,
name: 'John'
})
// 或者使用函数形式
counter.$patch((state) => {
state.count++
state.name = 'John'
})
}
// 监听状态变化
counter.$subscribe((mutation, state) => {
console.log(mutation.type, mutation.payload)
})
</script>
4.2 Pinia 进阶使用
重点掌握:
- 插件开发
- 持久化存储
- 状态重置
示例代码:
// stores/plugins/persistence.ts
import { PiniaPluginContext } from 'pinia'
export function persistencePlugin({ store }: PiniaPluginContext) {
// 从localStorage恢复状态
const savedState = localStorage.getItem(`${store.$id}-state`)
if (savedState) {
store.$state = JSON.parse(savedState)
}
// 监听状态变化并保存
store.$subscribe(({ storeId, state }) => {
localStorage.setItem(`${storeId}-state`, JSON.stringify(state))
})
}
// main.ts
const pinia = createPinia()
pinia.use(persistencePlugin)
// 自定义 Store 属性
pinia.use(({ store }) => {
store.customProperty = 'my custom value'
store.customMethod = () => console.log('hello')
})
4.3 组件通信方式一:Props 与 Emit
重点掌握:
- Props 定义与验证
- 事件发射与监听
- v-model 的使用
示例代码:
<!-- ChildComponent.vue -->
<template>
<div>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
<button @click="handleClick">Click Me</button>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
modelValue: string
label?: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
(e: 'customEvent', { id: number, value: string }): void
}>()
const handleClick = () => {
emit('customEvent', { id: 1, value: 'test' })
}
</script>
<!-- ParentComponent.vue -->
<template>
<div>
<ChildComponent
v-model="inputValue"
label="Username"
@customEvent="handleCustomEvent"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const inputValue = ref('')
const handleCustomEvent = (data) => {
console.log('Custom event received:', data)
}
</script>
4.4 组件通信方式二:Provide/Inject
重点掌握:
- 跨层级组件通信
- 响应式数据传递
- 作用域插槽替代方案
示例代码:
<!-- ParentComponent.vue -->
<template>
<div>
<slot :data="data" :updateData="updateData"></slot>
<ChildComponent />
</div>
</template>
<script setup>
import { provide, ref } from 'vue'
const data = ref({ count: 0 })
// 提供响应式数据
provide('data', data)
// 提供方法
const updateData = () => {
data.value.count++
}
provide('updateData', updateData)
// 提供只读数据
provide('readonlyData', readonly(data))
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<p>Count: {{ data.count }}</p>
<button @click="updateData">Update</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入数据和方法
const data = inject('data')
const updateData = inject('updateData')
// 使用默认值
const theme = inject('theme', 'light')
</script>
4.5 组件通信方式三:Event Bus (mitt)
重点掌握:
- 事件总线的使用
- 事件监听与清理
- 适用场景
示例代码:
// eventBus.ts
import mitt from 'mitt'
export const emitter = mitt()
// 类型定义
type Events = {
'item-click': { id: number; data: any }
'data-updated': void
}
export const typedEmitter = mitt<Events>()
// ComponentA.vue
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { emitter } from './eventBus'
// 监听事件
onMounted(() => {
emitter.on('item-click', (event) => {
console.log('Item clicked:', event)
})
})
// 清理事件监听
onUnmounted(() => {
emitter.off('item-click')
})
// 发送事件
const handleClick = () => {
emitter.emit('item-click', { id: 1, 'test' })
}
</script>
// ComponentB.vue
<script setup>
import { emitter } from './eventBus'
// 发送事件
const notifyUpdate = () => {
emitter.emit('data-updated')
}
</script>
4.6 异步组件与动态组件
重点掌握:
- 异步组件的加载
- 动态组件切换
- 加载状态处理
示例代码:
<template>
<div>
<!-- 异步组件 -->
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
<!-- 动态组件 -->
<component
:is="currentComponent"
@change="handleChange"
/>
<button @click="toggleComponent">Switch Component</button>
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
// 异步组件定义
const AsyncComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
// 动态组件
const components = {
'comp-a': defineAsyncComponent(() => import('./ComponentA.vue')),
'comp-b': defineAsyncComponent(() => import('./ComponentB.vue'))
}
const currentComponent = ref('comp-a')
const toggleComponent = () => {
currentComponent.value = currentComponent.value === 'comp-a' ? 'comp-b' : 'comp-a'
}
</script>