Pinia 知识点大梳理,结尾带你做一个属于自己的插件~
为什么需要状态管理
- Web 应用程序变得越来越复杂
- 组件和页面较多,在传递/更新数据时候很大概率会出错(数据流复杂)
Pinia 是什么?
官方文档介绍:pinia.vuejs.org/zh/introduc…
Pinia (发音为
/piːnjʌ/
,类似英文中的 “peenya”) 是最接近有效包名 piña (西班牙语中的 pineapple,即“菠萝”) 的词。 菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。 与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。 它(菠萝)也是一种原产于南美洲的美味热带水果。 -- 官方文档
一个 Store 应该包含可以在整个应用中访问的数据。这包括在许多地方使用的数据,例如显示在导航栏中的用户信息,以及需要通过页面保存的数据,例如一个非常复杂的多步骤表单。 -- 官方文档
Vue 中的状态管理
Vuex
- 学习成本高,比较复杂,难以理解
- 模块化管理不够直观,需要额外的学习成本
- 对 TS 支持不太好
- 存在性能问题
Pinia
- 学习成本较低,易于理解,更加直观和简洁
- 更好的 TypeScript 支持,添加 TS 更加容易
- 体积很小,只有 1KB 左右
- 支持多个 Store,没有 Vuex 复杂的模块化
你仍然可以通过导入和使用另一个 Store 来隐含地嵌套 stores 空间。虽然 Pinia 从设计上提供的是一个扁平的结构,但仍然能够在 Store 之间进行交叉组合。你甚至可以让 Stores 有循环依赖关系。 -- 官方文档
核心概念
- Store 整个仓库
- State (Data) 数据状态
- Action (Methods) 方法
- Getter (Computed) 计算值
Store
- Store 是一切东西的 “外壳“ ,Store 中包含了 State, Action, Getter
- pinia 中 store 各自独立,甚至可以交叉使用
- 另外注意的是,定义 Store 时,传入的 ID 要唯一
两种写法
// Option Store
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
},
},
})
// Setup Store
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment } // 必须使用 return 返回
})
// 这样写灵活性更高,在这里你可以使用 watch 以及其他有待探索的写法
创建完成之后就可以在组件/页面中使用:
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// 在 Setup Store 中,Store是使用 reactive 包裹,因此不需要加上 .value
console.log(store.count) // 0
</script>
解构时的一些注意点:
-
不能直接使用
const { count } = store
,这样会使得count
失去响应式 -
正确解构方法:
- 使用 Pinia 提供的方法:
import { storeToRefs } from 'pinia'
- 然后即可安全解构
const { count } = storeToRefs(store)
- 使用 Pinia 提供的方法:
-
另外需要注意的是
- 解构 State(Data) 以及 Getter(Computed) 时才需要使用
storeToRefs
,Action(Methods) 可以直接解构
- 解构 State(Data) 以及 Getter(Computed) 时才需要使用
State
数据状态是 Store 的核心
定义
在 Option Store 中,State 是一个返回初始状态的函数
import { defineStore } from 'pinia'
const useStore = defineStore('counter', { // 第二个参数是对象形式
// 为了完整类型推理,推荐使用箭头函数
state: () => {
return {
count: 0,
}
},
})
在 Setup Store 中,直接使用ref
定义你所需要的状态
import { defineStore } from 'pinia'
import { ref } from 'vue' // 别忘记导入 ref
const useStore = defineStore('counter', () => { // 注意第二个参数是函数形式
const count = ref(0)
return { count }
})
使用(setup语法)
Pinia 的便利无时无刻不能体现出来,如果你要访问 Store 中的数据,直接访问即可
const store = useStore()
// 1. 不需要像 vuex 那样进行繁琐的步骤,可以直接操作 store 中的数据
console.log(store.count) // 0
// 2. 该方法可以直接重置 state(这并不是清空 state,而是将其还原为默认值)
store.$reset()
// 3. 修改值
store.count++
// 或
store.$patch({
count: store.count + 1,
})
// 如果需要修改的 state 有很多,使用前者会损耗性能,而后者可以一次性更改多个属性
// 4. 替换整个 state
// 直接替换整个 state 会破坏响应性,应该使用 patch 来进行更改
store.$patch({ count: 666 })
// 或直接变更实例
pinia.state.value = { count: 666 }
// 5. 订阅 State
store.$subscribe((mutation, state) => {
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('count', JSON.stringify(state))
})
// 或
watch(pinia.state, (state) => {
localStorage.setItem('count', JSON.stringify(state))
},{ deep: true }
)
Getter
export const useStore = defineStore('counter', {
const count = ref(2)
const doubleCount = computed(() => count.value * 2)
return { count, doubleCount }
})
const store = useStore()
console.log(store.doubleCount) // 4
其他地方写法看官方文档即可,这里使用的是组合式 API 写法
Action
export const useStore = defineStore('counter', {
const count = ref(0)
function increase() {
count.value++
}
return { count, increase }
})
const store = useStore()
console.log(store.count) // 0
store.increase()
console.log(store.count) // 1
其他地方写法看官方文档即可,这里使用的是组合式 API 写法
你可以通过
store.$onAction()
来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after
表示在 promise 解决之后,允许你在 action 解决后执行一个一个回调函数。同样地,onError
允许你在 action 抛出错误或 reject 时执行一个回调函数。这些函数对于追踪运行时错误非常有用。 -- 官方文档
插件
一个持久化存储的简单案例
export function useStorage(context) {
// store: 目标 store
// option: 定义传给 defineStore() 的 store 的可选对象
const { store, options } = context
// 判断是否需要设置为持久化
if (options?.persist) {
let storage = localStorage
const storageList = [localStorage, sessionStorage, undefined]
// 判断存储方式是否存在
if (storageList.includes(options.storage)) {
storage = options.storage || localStorage
} else {
throw new Error(options.storage + '不存在')
}
// 若有本地存储,使用存储的值;否则使用默认值,并存储
if (!!storage.getItem(store.$id)) {
store.$state = JSON.parse(storage.getItem(store.$id))
} else {
storage.setItem(store.$id, JSON.stringify(store.$state))
}
// 监听数据变化,存储到自定义的 storage 中
store.$subscribe(() => {
storage.setItem(store.$id, JSON.stringify(store.$state))
})
}
}
在注册 Pinia 的时候注册该插件:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { useStorage } from './plugin/storage' // 引入插件
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
pinia.use(useStorage) // 注册插件
app.use(pinia)
app.mount('#app')
在 Store 中加入自定义配置:
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore(
'counter',
() => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
return { count, doubleCount, increment, decrement }
},
{ persist: true, storage: sessionStorage } // 传入的 options
)
推荐库:Pinia 持久化存储插件(1000 stars): prazdevs.github.io/pinia-plugi…(参与翻译全部中文文档)
转载自:https://juejin.cn/post/7222115117432995877