Pinia 是 Vue 的状态管理库,它提供了一种更简单、更不规范的 API 来管理应用的状态。Pinia 的设计哲学是简单性和易用性,它避免了 Vuex 中的许多复杂概念,如 mutations 和模块的嵌套结构,提供了一种更现代、更符合 Vue 3 Composition API 风格的状态管理方式。
先来瞅一眼 Pinia 的核心组件主要包括以下几个方面:
-
Store:Pinia 中的 Store 是一个保存状态和业务逻辑的实体,它不与组件树绑定,可以在整个应用中访问。Store 包含三个核心概念:state(状态)、getters(获取器)、actions(动作)。这些概念类似于组件中的 data、computed 和 methods。
-
defineStore() :这是定义 Store 的函数,需要传入一个独一无二的名字作为 ID,用于连接 Store 和 devtools。Store 可以通过 Setup 函数或 Option 对象来定义。
-
reactive:虽然 Pinia 本身不要求使用 Composition API,但它与 Vue 的 reactive 系统紧密集成,允许开发者以声明式的方式管理状态。
-
Devtools 支持:Pinia 支持 Vue Devtools,提供了追踪 actions 和 mutations 的时间线、时间旅行和热更新等功能,方便开发者调试。
-
插件系统:Pinia 允许通过插件来扩展其功能,例如支持服务器端渲染或添加额外的中间件。
-
TypeScript 支持:Pinia 提供了对 TypeScript 的良好支持,包括类型推断和自动补全功能,使得在 TypeScript 项目中使用 Pinia 更加方便 。
-
SSR 支持:Pinia 支持服务端渲染,这对于构建需要 SSR 的应用是一个重要的特性。
-
映射辅助函数:Pinia 提供了如 mapStores、mapState 和 mapActions 等辅助函数,使得在组件中使用 Store 更加方便。
下面是每个部分的详细介绍。
1. Store
在 Pinia 中,Store
是用来封装应用的状态和逻辑的核心概念。它允许你将状态和行为集中管理,而不是分散在各个组件中。Store
可以包含以下几部分:
- state:状态数据,通常是响应式的,可以在组件中被读取和修改。
- getters:计算属性,用于派生状态,它们是响应式的,并且可以被缓存。
- actions:可以包含任意的异步操作或同步操作,用于修改状态或执行复杂的业务逻辑。
案例实现
下面是一个创建 Store
的步骤解析,包括代码示例:
步骤 1: 定义 Store
首先,你需要从 Pinia 导入 defineStore
函数,并使用它来定义一个新的 Store
。
import { defineStore } from 'pinia'export const useUserStore = defineStore('user', {state: () => ({users: [] // 初始状态是一个空数组}),getters: {// 一个 getter 函数,返回数组中的用户数量count: (state) => state.users.length},actions: {// 一个 action 函数,用于添加用户addUser(user) {this.users.push(user)}}
})
步骤 2: 在组件中使用 Store
在 Vue 组件中,你可以通过调用你定义的 useUserStore
函数来使用这个 Store
。
<template><div><button @click="addNewUser">添加用户</button><p>用户总数: {{ userCount }}</p><ul><li v-for="user in users" :key="user.id">{{ user.name }}</li></ul></div>
</template><script setup>
import { computed } from 'vue'
import useUserStore from '@/stores/user'const store = useUserStore()
const users = computed(() => store.users)
const userCount = computed(() => store.count)function addNewUser() {store.addUser({ id: Date.now(), name: '新用户' })
}
</script>
代码解析
- 导入
defineStore
:从 Pinia 导入defineStore
函数来定义新的Store
。 - 定义
useUserStore
:创建一个名为useUserStore
的函数,它返回一个Store
对象。 - state:在
Store
中定义了一个状态users
,初始为空数组。 - getters:定义了一个
count
getter,它返回users
数组的长度。 - actions:定义了一个
addUser
action,它接受一个用户对象并将其添加到users
数组中。 - 在组件中使用:在组件的
<script setup>
块中,通过调用useUserStore
来获取Store
实例,并使用computed
来创建响应式的users
和userCount
。 - 添加用户:定义了一个
addNewUser
函数,当按钮被点击时,调用store.addUser
来添加新用户。
以上案例展示了如何在 Pinia 中创建和管理状态,以及如何在 Vue 组件中使用 Store
。通过这种方式,你可以集中管理状态,使得状态逻辑更加清晰和可维护。
2. defineStore()
defineStore()
是 Pinia 中用于定义 Store 的函数。它允许你以声明式的方式创建一个状态管理单元,这个单元可以包含状态(state)、获取器(getters)、动作(actions)等。defineStore()
函数接受一个唯一的 ID 和一个配置对象,配置对象中可以定义 state、getters、actions 等属性。
案例实现
下面是一个使用 defineStore()
创建 Store 的步骤解析,包括代码示例:
步骤 1: 导入 defineStore
首先,需要从 Pinia 导入 defineStore
函数。
import { defineStore } from 'pinia'
步骤 2: 定义 Store
使用 defineStore()
定义一个新的 Store,传入一个唯一的 ID 和一个配置对象。
export const useCartStore = defineStore('cart', {state: () => ({items: [] // 购物车初始状态为空数组}),getters: {// 计算属性,返回购物车中商品的总数量itemCount(state) {return state.items.reduce((total, item) => total + item.quantity, 0)}},actions: {// 添加商品到购物车的动作addItem(item) {const index = this.items.findIndex(i => i.id === item.id)if (index > -1) {// 如果商品已存在,则增加数量this.items[index].quantity += item.quantity} else {// 否则,添加新商品this.items.push(item)}},// 清空购物车的动作clearCart() {this.items = []}}
})
步骤 3: 在组件中使用 Store
在 Vue 组件中,通过调用 useCartStore
来使用这个 Store。
<template><div><button @click="addItem">添加商品</button><button @click="clearCart">清空购物车</button><p>商品总数: {{ itemCount }}</p><ul><li v-for="item in cartItems" :key="item.id">{{ item.name }} - 数量: {{ item.quantity }}</li></ul></div>
</template><script setup>
import { computed } from 'vue'
import useCartStore from '@/stores/cart'const store = useCartStore()
const cartItems = computed(() => store.items)
const itemCount = computed(() => store.itemCount)function addItem() {store.addItem({ id: 1, name: '商品A', quantity: 1 })
}function clearCart() {store.clearCart()
}
</script>
代码解析
- 导入 defineStore:从 Pinia 导入
defineStore
函数。 - 定义 useCartStore:创建一个名为
useCartStore
的函数,它返回一个配置好的Store
对象。 - state:在
Store
中定义了一个状态items
,初始为空数组,用于存储购物车中的商品。 - getters:定义了一个
itemCount
getter,它通过遍历items
数组并累加每个商品的quantity
来计算总数量。 - actions:定义了
addItem
和clearCart
两个 actions。addItem
用于向购物车添加商品,如果商品已存在则增加其数量;clearCart
用于清空购物车。 - 在组件中使用:在组件的
<script setup>
块中,通过调用useCartStore
来获取Store
实例,并使用computed
来创建响应式的cartItems
和itemCount
。 - 添加和清空商品:定义了
addItem
和clearCart
函数,分别用于添加商品和清空购物车。
使用 defineStore()
创建一个包含状态、获取器和动作的 Store,并在 Vue 组件中使用这个 Store 来管理购物车的状态。通过这种方式,你可以将状态逻辑封装在 Store 中,使得组件更加简洁和易于管理。
3. reactive
reactive()
是 Vue 3 的 Composition API 中的一个函数,它用于创建响应式的状态对象。当使用 reactive()
创建一个对象后,Vue 会追踪这个对象中属性的读取和修改,并且在数据变化时通知依赖于这些数据的组件重新渲染。
Pinia 与 Vue 的响应式系统紧密集成,reactive()
通常在定义 Store 的状态时使用。在 Pinia 中,状态(state)是一个通过 reactive()
创建的响应式对象,因此任何对状态的修改都会自动触发与该状态相关的组件更新。
案例实现
下面是一个使用 reactive()
来创建响应式状态的步骤解析:
步骤 1: 导入 reactive
首先,需要从 Vue 导入 reactive
函数。
import { reactive } from 'vue'
步骤 2: 使用 reactive 创建状态
使用 reactive()
函数来创建一个响应式的状态对象。
const state = reactive({count: 0, // 初始状态message: 'Hello, Pinia!' // 初始消息
})
步骤 3: 在 Pinia Store 中使用 reactive 状态
在 Pinia 的 defineStore()
中,可以直接使用 reactive()
来定义状态。
import { defineStore } from 'pinia'
import { reactive } from 'vue'export const useMyStore = defineStore('myStore', {state: () => reactive({count: 0,message: 'Hello, Pinia!'}),// 其他 getters 和 actions 可以在这里定义
})
步骤 4: 在组件中使用 Store
在 Vue 组件中,通过调用 useMyStore
来使用这个 Store,并访问响应式状态。
<template><div><p>Count: {{ count }}</p><p>Message: {{ message }}</p><button @click="increment">Increment</button></div>
</template><script setup>
import { computed } from 'vue'
import useMyStore from '@/stores/myStore'const store = useMyStore()
const count = computed(() => store.state.count)
const message = computed(() => store.state.message)function increment() {store.state.count++
}
</script>
代码解析
- 导入 reactive:从 Vue 导入
reactive
函数。 - 创建响应式状态:使用
reactive()
创建一个包含count
和message
的响应式状态对象。 - 在 Pinia Store 中使用 reactive:在
defineStore()
的state
函数中返回一个reactive()
对象,这样 Pinia 就可以管理这个状态的响应性。 - 在组件中使用:在组件的
<script setup>
块中,通过调用useMyStore
来获取Store
实例。使用computed
来确保访问状态时保持响应性。 - 修改状态:定义了一个
increment
函数,当按钮被点击时,直接修改store.state.count
,这会触发组件的更新。
通过这种方式,你可以确保状态的任何变化都会自动传播到使用这些状态的组件中,实现响应式的数据流,你get到了吗。
4. Devtools 支持
Vue Devtools 是一个浏览器扩展,它为开发 Vue 应用提供了强大的调试支持。对于 Pinia 来说,Devtools 支持意味着你可以在开发过程中更直观地查看和操作应用的状态。
Pinia 与 Vue Devtools 集成,提供了以下功能:
- 状态查看:可以在 Devtools 中查看所有 Pinia Store 的状态。
- 时间旅行:回溯状态的历史,查看状态的变化过程。
- 动作追踪:记录和展示 Store 中 actions 的调用历史,包括参数和执行时间。
- 状态修改:允许你在 Devtools 中直接修改状态,这些修改会实时反映到应用中。
- 持久化状态:在开发过程中保持状态不变,即使页面刷新或组件重新渲染。
案例实现
要充分利用 Pinia 的 Devtools 支持,你需要确保正确安装和配置了 Vue Devtools,并且正确地在你的 Pinia Store 中编写代码。来吧,一步一步跟着做就行:
步骤 1: 安装 Vue Devtools
首先,确保你已经安装了 Vue Devtools 浏览器扩展。你可以从 Chrome Web Store 或 Firefox Add-ons 等地方安装。
步骤 2: 创建 Pinia Store
创建一个 Pinia Store,并定义 state、getters 和 actions。
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})
步骤 3: 在 Vue 应用中使用 Pinia
在你的 Vue 应用中创建 Pinia 实例,并在应用启动时使用它。
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const pinia = createPinia()
const app = createApp(App)app.use(pinia)
app.mount('#app')
步骤 4: 使用 Store
在组件中使用你的 Store,并执行一些状态更改的动作。
<template><div><p>Count: {{ count }}</p><button @click="increment">Increment</button></div>
</template><script setup>
import { computed } from 'vue'
import useCounterStore from '@/stores/counter'const store = useCounterStore()
const count = computed(() => store.count)function increment() {store.increment()
}
</script>
步骤 5: 使用 Vue Devtools 调试
- 打开浏览器的开发者工具。
- 切换到 Vue Devtools 面板。
- 在“Components”或“Store”标签页中,你应该能看到你的 Pinia Store。
- 展开 Store,查看 state、getters 和 actions。
- 点击 actions,你可以看到它们的调用历史和参数。
- 直接在 Devtools 中修改 state 的值,看应用如何响应这些变化。
代码过程步骤解析
- 安装 Devtools:确保浏览器扩展已安装。
- 创建 Store:使用
defineStore
创建一个包含 state、getters 和 actions 的 Pinia Store。 - 应用配置:在应用启动时创建 Pinia 实例并使用它。
- 组件使用:在组件中通过调用 Store 函数来获取 Store 实例,并使用 computed 属性来保持响应性。
- 调试:使用 Vue Devtools 查看和修改状态,追踪 actions。
利用 Vue Devtools 提供的功能来调试使用 Pinia 管理状态的 Vue 应用,感觉是不是挺爽。
5. 插件系统
Pinia 的插件系统允许开发者扩展 Pinia 的功能。插件可以访问 Pinia 的 Store 创建过程,可以执行以下操作:
- 在 Store 创建之前或之后执行自定义逻辑。
- 拦截 Store 的
state
、getters
、actions
等属性。 - 向 Store 添加自定义属性或方法。
- 处理 Store 的销毁逻辑。
Pinia 插件通常在创建 Pinia 实例时注册,然后 Pinia 会将插件应用到每个创建的 Store 上。
案例实现
下面是一个创建和使用 Pinia 插件的步骤解析,包括代码示例:
步骤 1: 定义插件
首先,定义一个插件函数,该函数接收 Pinia 的实例作为参数。
function myPiniaPlugin(pinia) {// 插件逻辑
}
步骤 2: 插件逻辑
在插件函数内部,可以访问 Pinia 的 store
对象,并对其进行操作。
function myPiniaPlugin(pinia) {pinia.use((store) => {// 可以在此处访问 store.state, store.getters, store.actions 等// 例如,向 store 添加一个新属性store.myCustomProperty = 'Hello from plugin!'})
}
步骤 3: 创建 Pinia 实例并使用插件
创建 Pinia 实例时,使用 use
方法注册插件。
import { createPinia } from 'pinia'const pinia = createPinia().use(myPiniaPlugin)
步骤 4: 创建 Store
定义一个 Store,使用 defineStore
函数。
import { defineStore } from 'pinia'export const useMyStore = defineStore('myStore', {// state, getters, actions 定义state: () => ({value: 0}),// 其他选项...
})
步骤 5: 在组件中使用 Store
在组件中使用 Store,并访问插件添加的属性。
<template><div><p>Value: {{ value }}</p><p>Plugin Property: {{ store.myCustomProperty }}</p></div>
</template><script setup>
import { computed } from 'vue'
import useMyStore from '@/stores/myStore'const store = useMyStore()
const value = computed(() => store.value)
</script>
步骤 6: 组件中使用插件属性
在组件的模板或脚本中,使用插件添加到 Store 的自定义属性。
// 在模板中
<p>Plugin Property: {{ store.myCustomProperty }}</p>// 在脚本中
console.log(store.myCustomProperty) // 输出: Hello from plugin!
代码过程步骤解析
- 定义插件:创建一个函数,该函数接收 Pinia 实例并定义插件逻辑。
- 插件逻辑:在插件函数内部,使用
pinia.use
方法注册一个回调,该回调接收每个 Store 并可以对其进行操作。 - 注册插件:在创建 Pinia 实例时,通过
.use()
方法注册插件。 - 创建 Store:使用
defineStore
定义 Store,包括 state、getters、actions。 - 使用 Store:在组件中使用 Store,并利用 computed 属性保持响应性。
- 访问插件属性:在组件的模板或脚本中,访问插件添加到 Store 的自定义属性。
记住这一点,开发者需要添加自定义逻辑和属性,通过Pinia 插件系统就 OK。
6. TypeScript 支持
Pinia 为 TypeScript 用户提供了一流的支持,确保类型安全和开发体验。Pinia 的 TypeScript 支持主要体现在以下几个方面:
- 类型推断:Pinia 利用 TypeScript 的高级类型系统来推断 Store 中的状态、getters 和 actions 的类型。
- 类型声明:Pinia 提供了类型声明文件(
.d.ts
),确保 Pinia API 在 TypeScript 项目中的类型正确性。 - 自动补全:在 TypeScript 环境中,编辑器可以提供自动补全功能,帮助开发者快速编写代码。
- 类型守卫:Pinia 与 TypeScript 配合使用时,可以利用类型守卫来确保对 Store 属性的访问是安全的。
案例实现
下面是一个使用 TypeScript 与 Pinia 结合使用的步骤解析,包括代码示例:
步骤 1: 设置 TypeScript 环境
确保你的项目已经配置了 TypeScript,并且安装了必要的类型声明文件。
npm install typescript @vue/compiler-sfc
步骤 2: 定义 Store 使用 TypeScript
使用 TypeScript 的类型定义来创建 Pinia Store。
import { defineStore } from 'pinia'interface State {count: numbermessage: string
}export const useMyStore = defineStore('myStore', {state: (): State => ({count: 0,message: 'Hello, Pinia with TypeScript!'}),getters: {// 使用 TypeScript 来声明 getter 的返回类型doubleCount: (state): number => state.count * 2},actions: {increment(): void {this.count++}}
})
步骤 3: 在组件中使用 Store
在 Vue 组件中使用 Store,并利用 TypeScript 提供类型安全。
<template><div><p>Count: {{ count }}</p><p>Message: {{ message }}</p><p>Double Count: {{ doubleCount }}</p><button @click="increment">Increment</button></div>
</template><script setup lang="ts">
import { computed } from 'vue'
import useMyStore from '@/stores/myStore'const store = useMyStore()
const count = computed(() => store.count)
const message = computed(() => store.message)
const doubleCount = computed(() => store.doubleCount)function increment() {store.increment()
}
</script>
步骤 4: 利用类型守卫
使用类型守卫来确保对 Store 属性的访问是安全的。
if (store.hasOwnProperty('count')) {// TypeScript 知道 'count' 存在且为 number 类型console.log(store.count)
}
代码过程步骤解析
- 配置 TypeScript:确保项目中安装了 TypeScript 及其类型声明文件。
- 定义 Store 类型:使用 TypeScript 的接口(
interface
)来定义 Store 的状态类型。 - 创建 Store:使用
defineStore
并传入类型化的 state 函数,以及声明了返回类型的 getters 和 actions。 - 组件中使用 Store:在组件中使用 TypeScript 来确保访问 Store 属性的类型安全。
- 类型守卫:使用 TypeScript 的
hasOwnProperty
方法和类型守卫来安全地访问 Store 的属性。
小结一下,Pinia 与 TypeScript 结合使用可以提供类型安全的状态管理,同时编辑器的自动补全和类型检查功能可以提高开发效率和减少错误。
7. SSR 支持
服务器端渲染(SSR)是一种将网站页面在服务器上生成并发送给客户端的技术。对于状态管理库来说,SSR 支持意味着可以在服务器上初始化和操作状态,然后将状态序列化后发送到客户端,客户端再将这些状态恢复以保持与服务器端相同的状态。
Pinia 对 SSR 的支持主要体现在以下几个方面:
- 状态序列化:Pinia 允许将状态(state)序列化成 JSON 字符串,以便在服务器和客户端之间传输。
- 状态恢复:客户端可以接收服务器端的状态,并将其恢复到对应的 Store 中。
- 无状态的 Store 定义:Pinia 的 Store 定义是无状态的,这意味着在服务器端和客户端端都可以重新创建 Store。
- 与 Vue SSR 的集成:Pinia 可以与 Vue 的 SSR 系统集成,确保在服务器端预渲染组件时使用正确的状态。
案例实现
下面是一个使用 Pinia 进行 SSR 的步骤解析,包括代码示例:
步骤 1: 定义 Store
首先,定义一个 Pinia Store。
import { defineStore } from 'pinia'export const useMyStore = defineStore('myStore', {state: () => ({count: 0}),// 其他选项...
})
步骤 2: 在服务器端创建和序列化状态
在服务器端,创建 Store 实例并初始化状态,然后将状态序列化。
// server.js
import { createPinia } from 'pinia'
import useMyStore from '@/stores/myStore'const pinia = createPinia()// 模拟从数据库获取初始状态
const initialState = { count: 10 }// 创建 Store 实例并设置初始状态
const store = useMyStore(pinia)
store.$state.count = initialState.count// 序列化状态
const stateToTransfer = JSON.stringify(store.$state)
步骤 3: 在客户端恢复状态
在客户端,接收服务器端发送的状态,然后恢复到对应的 Store 中。
// client.js
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue' // Vue 应用的根组件
import useMyStore from '@/stores/myStore'const pinia = createPinia()// 假设从服务器接收的状态如下
const stateFromServer = JSON.parse(/* state serialized from server */)// 创建 Store 实例并恢复状态
const store = useMyStore(pinia)
store.$state.count = stateFromServer.countconst app = createApp(App)
app.use(pinia)
app.mount('#app')
步骤 4: 在 Vue 组件中使用 Store
在 Vue 组件中,像平常一样使用 Store。
<template><div>{{ count }}</div>
</template><script setup>
import { computed } from 'vue'
import useMyStore from '@/stores/myStore'const store = useMyStore()
const count = computed(() => store.count)
</script>
代码过程步骤解析
- 定义 Store:使用
defineStore
定义一个 Pinia Store。 - 服务器端创建和序列化:在服务器端,创建 Store 实例,设置初始状态,并使用
JSON.stringify
序列化状态。 - 客户端恢复状态:在客户端,解析服务器端发送的状态字符串,并恢复到 Store 的
$state
中。 - 组件中使用 Store:在 Vue 组件中,通过调用 Store 函数获取 Store 实例,并使用 computed 属性来访问状态。
一句话,Pinia支持SSR有助于提高应用的初始加载性能和SEO优化。
8. 映射辅助函数
在 Pinia 中,映射辅助函数用于将 Store 中的状态(state)、获取器(getters)、动作(actions)映射到组件的计算属性(computed properties)、方法(methods)或响应式属性上。这些辅助函数提供了一种便捷的方式来使用 Store,而无需在每个组件中重复编写相同的代码。
Pinia 的映射辅助函数主要包括:
mapState
:将 Store 中的状态映射为组件的计算属性。mapGetters
:将 Store 中的获取器映射为组件的计算属性。mapActions
:将 Store 中的动作映射为组件的方法。
案例实现
下面是一个使用 Pinia 映射辅助函数的步骤解析,包括代码示例:
步骤 1: 定义 Store
首先,定义一个 Pinia Store。
import { defineStore } from 'pinia'export const useCartStore = defineStore('cart', {state: () => ({items: []}),getters: {itemCount: (state) => state.items.length},actions: {addItem(item) {this.items.push(item)}}
})
步骤 2: 使用映射辅助函数
在组件中,使用 mapState
、mapGetters
和 mapActions
将 Store 的属性映射到组件上。
<template><div><p>Item Count: {{ itemCount }}</p><button @click="addItem({ id: 1, name: 'Apple' })">Add Apple</button></div>
</template><script setup>
import { mapState, mapGetters, mapActions } from 'pinia'
import useCartStore from '@/stores/cart'const store = useCartStore()// 使用 mapState 映射状态
const items = mapState(store, 'items')// 使用 mapGetters 映射获取器
const itemCount = mapGetters(store, 'itemCount')// 使用 mapActions 映射动作
const { addItem } = mapActions(store, ['addItem'])
</script>
步骤 3: 在模板中使用映射的属性
在组件的模板中,直接使用映射的计算属性和方法。
<p>Item Count: {{ itemCount }}</p>
<button @click="addItem({ id: 1, name: 'Apple' })">Add Apple</button>
代码过程步骤解析
- 定义 Store:使用
defineStore
定义一个包含状态、获取器和动作的 Pinia Store。 - 使用映射辅助函数:
mapState
:将items
状态映射为组件的计算属性。mapGetters
:将itemCount
获取器映射为组件的计算属性。mapActions
:将addItem
动作映射为组件的方法。
- 模板中使用:在模板中,使用映射的计算属性
itemCount
来显示项目数量,使用映射的方法addItem
来添加新项目。
使用映射辅助函数,Pinia 可以让开发者以声明式的方式在组件中使用 Store 的状态和行为,从而减少样板代码并提高组件的可读性和可维护性。此外,这些辅助函数还有助于保持响应性,确保当 Store 中的状态变化时,组件能够自动更新。
最后
不得不说,Pinia 提供了一种灵活且高效的方式来管理 Vue 应用的状态,无论是在单页应用还是服务器端渲染的场景下,都有出色的表现,写完收工,欢迎关注威哥爱编程,一起走全栈之路。