Pinia 是 Vue 官方团队推荐代替Vuex的一款轻量级状态管理库,允许跨组件/页面共享状态。
Pinia 旨在提供一种更简洁、更直观的方式来处理应用程序的状态。
Pinia 充分利用了 Vue 3 的 Composition API。
官网:
Pinia符合直觉的 Vue.js 状态管理库
store
:是存储状态(共享数据)的地方。
use
开头。例如:useCountStore
、useUserStore
,useCartStore
,useProductStore
。state
:是 store 中用于存储应用状态的部分。
state
是真正存储数据的地方,它就是存放在store里的数据。state
写成函数形式,并且要return
一个对象。state() { return {} }
getters
:从存储的状态中派生数据,类似于 Vue 中的计算属性(computed
)。
actions
:是用于改变状态的方法。npm install pinia
# 或者使用 yarn
yarn add pinia
main.js
或main.ts
),引入并使用Pinia:// 引入 createApp 用于创建实例
import {
createApp } from 'vue';
// 引入 App.vue 根组件
import App from './App.vue';
// 从 Pinia 库中引入 createPinia 函数,用于创建 Pinia 实例
import {
createPinia } from 'pinia';
// 创建一个应用
const app = createApp(App)
// 创建 Pinia 实例
const pinia = createPinia();
// 将 Pinia 实例注册到 Vue 应用实例中,使得整个应用可以使用 Pinia 进行状态管理
app.use(pinia);
// 挂载整个应用到app容器中
app.mount('#app')
通过以上步骤,成功地在 Vue 项目中集成了 Pinia 状态管理库,为应用提供了集中式的状态管理功能,可以在组件中通过使用 Pinia 的 store 来管理和共享数据。
Store 是一个保存状态和业务逻辑的实体。它承载着全局状态。
Pinia 使用 defineStore
定义Store。
import {
defineStore } from 'pinia'
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useCountStore = defineStore('count', {
// 其他配置...
})
defineStore()
defineStore('count', {})
中的count
就是这个store的ID。state
、actions
、getters
。与 Vue 的选项式 API 类似,可以传入一个带有 state
、actions
与 getters
属性的 Option 对象。
export const useCountStore = defineStore('count', {
state: () => ({
count: 0 }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
可以认为 state
是 store 的数据 (data
),getters
是 store 的计算属性 (computed
),而 actions
则是方法 (methods
)。
与 Vue 组合式 API 的 setup
函数 相似,可以传入一个函数,该函数定义了一些响应式属性和方法,并且 return
一个带有需要暴露出去的属性和方法的对象。
import {
defineStore } from "pinia";
export const useCountStore = defineStore('count', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
// 把要在组件中使用到的属性、方法暴露出去
return {
count, doubleCount, increment }
})
在 Setup Store 中:
ref()
就是 state
属性computed()
就是 getters
function()
就是 actions
注意,要让 pinia 正确识别 state
,你必须在 setup store 中返回 state
的所有属性。这意味着,你不能在 store 中使用私有属性。不完整返回会影响 SSR ,开发工具和其他插件的正常运行。
虽然定义了一个 store,但在使用 调用
useStore()
(或者使用 setup()
函数) 之前,store 实例是不会被创建的:
Pinia中,没有名为 count
的store。
调用 useStore()
后,Pinia 自动将store安装到vue应用中:
<script setup lang="ts">
import {
useCountStore } from '@/store/count';
// 调用useCountStore函数得到一个countStore实例
// 一旦 store 被实例化,可以直接访问在 store 的 state、getters 和 actions 中定义的任何属性。
// 调用useCountStore后,Pinia 自动将store安装到vue应用中
const countStore = useCountStore()
console.log(countStore) // 一个reactive对象
console.log(countStore.count) // 0
script>
通过工具vue devtools查看Pinia,名为count
的store已经被安装到vue应用中:
通过工具vue devtools查看Count.vue
:
在Count.vue
组件里:
countStore
是一个 reactive
定义的响应式对象。
sum
是一个Ref(响应式引用)类型的数据。
通过实例countStore
访问state
的count
属性:
// 直接访问, 不需要使用.value
const count1 = countStore.count;
// 通过访问 store 实例的 $state 属性来获取状态值
const count2 = constStore.$state.count
// 解构 constStore
const {
count } = constStore
每个 store 都被 reactive
包装过,所以可以自动解包任何它所包含的 Ref(ref()
、computed()
…)。
reactive
对象包含了 ref
类型的数据,直接访问这个 ref
数据时不需要使用 .value
。
reactive
对象中的 ref
数据时,Vue 会自动解包 ref
的值,就可以直接获取到 ref
所包裹的值,而无需显式地使用 .value
。count
属性失去响应性,值始终为 0
。不会随着 store 中的状态变化而自动更新。使用 storeToRefs()
解构store,解构后的属性保持响应性。它将为每一个响应式属性创建引用。
<script setup>
import {
storeToRefs } from 'pinia';
import {
useCountStore } from '@/store/count';
const countStore = useCountStore()
// `count` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const {
count, doubleCount } = storeToRefs(countStore)
// 作为 action 的 increment 可以直接解构
const {
increment } = countStore
script>
执行console.log(storeToRefs(countStore))
,看看控制台打印结果:
storeToRefs()
只关注store里的数据,不关注store里的方法,不会对方法进行ref
包装。
解构出来的属性都是放在state
、getter
里面的数据。
为什么不使用toRefs()
解构store呢?
toRefs()
也可以解构store,但是它会把store的全部属性(数据、方法)变成ref类型。
执行console.log(toRefs(countStore))
,看看控制台打印结果:
在大多数情况下,state 都 store 的核心。
在 Pinia 中,state 被定义为一个返回初始状态的函数。
import {
defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 为了完整类型推理,推荐使用箭头函数
state: () => {
return {
// 所有这些属性都将自动推断出它们的类型
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
如果在tsconfig.json
中启用了 strict
,或者启用了 noImplicitThis
,Pinia 将自动推断变量的状态类型。
在某些情况下,需要使用类型断言:
const useStore = defineStore('storeId', {
state: () => {
return {
// 用于初始化空列表
userList: [] as UserInfo[],
// 用于尚未加载的数据
user: null as UserInfo | null,
}
},
})
interface UserInfo {
name: string
age: number
}
userList: [] as UserInfo[]
:
userList: []
:这部分将userList
初始化为一个空数组。在应用启动时,这个属性没有任何值,所以初始化为一个空数组可以确保有一个明确的初始状态。as UserInfo[]
:类型断言,明确指定userList
的类型为UserInfo[]
,即一个由UserInfo
类型元素组成的数组。userList
时,TypeScript 可以进行类型检查,确保只向数组中添加符合UserInfo
类型的元素,减少类型错误的发生。user: null as UserInfo | null
:
user: null
:将user
初始化为null
。这表示在应用启动时,还没有特定的用户信息被加载或设置,所以初始值为null
。as UserInfo | null
:类型断言,明确指定user
的类型为UserInfo | null
。这意味着user
可以是一个符合UserInfo
类型的对象,也可以是null
。user
的操作符合其类型定义。例如,如果尝试将一个不兼容的类型赋值给user
,TypeScript 会报错,从而避免运行时错误。可以用一个接口定义 state,并添加 state()
的返回值的类型:
interface State {
userList: UserInfo[]
user: UserInfo | null
}
const useStore = defineStore('storeId', {
state: (): State => {
return {
userList: [],
user: null,