Pinia-状态管理
特点:
1. 轻量和模块化
- Pinia 是一个轻量级的状态管理库,支持模块化管理,即可以将应用的状态分成多个 store 以实现更好的组织。
- 使用 Pinia,可以定义多个 store,每个 store 都是一个独立的模块,负责管理其自己的状态、操作和计算属性(类似 Vuex 的模块化,但更加简单直观)。
2. 组合式 API 和逻辑隔离
- Pinia 提供的 API 更符合 Vue 3 的组合式 API(Composition API)风格,状态管理逻辑与 Vue 3 的特性深度整合。
- 通过组合式 API,使用起来更加灵活和直观,避免了在大型应用中常见的命名冲突问题。
3. 直观的持久化状态(支持 SSR)
- Pinia 支持服务端渲染(SSR)和状态持久化,使得在服务端渲染应用中共享状态变得简单。
- 使用 Pinia 时可以轻松地将 store 的状态持久化到本地存储中,以在页面刷新后保持数据。
4. 类型安全支持
- 对于 TypeScript 用户来说,Pinia 提供了良好的类型推导和自动完成功能。它的 API 设计让类型支持更加顺畅,因此在写 TypeScript 项目时可以获得更好的开发体验。
5. 更简单的 API
- Pinia 的 API 更简洁,移除了 Vuex 中的 mutation,直接使用 action 修改状态。这样可以减少心智负担,并且更加贴近 Vue 的设计哲学。
- 没有像 Vuex 那样的 “commit” 或 “dispatch” 的复杂逻辑,Pinia 使用起来更加直观。
6. 热更新
- Pinia 支持热更新(hot module replacement, HMR),使得在开发环境下对状态管理代码的修改可以直接生效,无需刷新页面。
定义Store
Store 是用 defineStore()
定义的,它的第一个参数要求是一个独一无二的名字:
import { defineStore } from 'pinia'
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {
// 其他配置...
})
这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 use… 是一个符合组合式函数风格的约定。
defineStore()
的第二个参数可接受两类值:Setup 函数或 Option 对象。
Option Store
传入一个带有 state
、actions
与 getters
属性的 Option 对象
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'Eduardo' }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
可以认为 state
是 store 的数据 (data
),getters
是 store 的计算属性 (computed
),而 actions
则是方法 (methods
)。
Setup Store
与 Vue 组合式 API 的 setup 函数
相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
使用Store
注意,store
是一个用 reactive
包装的对象,这意味着不需要在 getters 后面写 .value
。就像 setup
中的 props
一样,我们不能对它进行解构:
<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'
const store = useCounterStore()
// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
//const { name, doubleCount } = store
//name // 将始终是 "Eduardo"
//doubleCount // 将始终是 0
setTimeout(() => {
store.increment()
}, 1000)
// ✅ 这样写是响应式的
// 💡 当然你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>
为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()
。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>
State
在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 为了完整类型推理,推荐使用箭头函数
state: () => {
return {
// 所有这些属性都将自动推断出它们的类型
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
访问 state
默认情况下,你可以通过 store
实例访问 state,直接对其进行读写。
const store = useStore()
store.count++
注意,新的属性如果没有在 state()
中被定义,则不能被添加。它必须包含初始状态。例如:如果 secondCount
没有在 state()
中定义,我们无法执行 store.secondCount = 2
。
Getter
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
// 自动推断出返回类型是一个 number
doubleCount(state) {
return state.count * 2
},
// 返回类型**必须**明确设置
doublePlusOne(): number {
// 整个 store 的 自动补全和类型标注 ✨
return this.doubleCount + 1
},
},
})
用 computed函数 进行模拟
//state
const count = ref(0)
//getter
const doubleCount = computed(() => count.value * 2)
Action
Action 相当于组件中的 method。它们可以通过 defineStore()
中的 actions
属性来定义,并且它们也是定义业务逻辑的完美选择。
export const useCounterStore = defineStore('main', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
类似 getter,action 也可通过
this
访问整个 store 实例,并支持完整的类型标注(以及自动补全✨)。不同的是,action
可以是异步的,你可以在它们里面await
调用任何 API,以及其他 action!
setup写法:
Pinia持久化
官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/
- 安装插件 pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate
- 使用 main.js
import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
- 配置 store/counter.js
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
...
return {
count,
doubleCount,
increment
}
}, {
persist: true
})