目录
选项式API通过$reset()方法将state重置为初始值
简介
符合直觉的 Vue.js 状态管理库,类型安全、可扩展性以及模块化设计。 甚至让你忘记正在使用的是一个状态库。
安装
yarn add pinia
# 或者使用 npm
npm install pinia
创建pinia实例
src/main.ts中创建并使用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')
Store
Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念,state,getter和 action,这些概念相当于组件中的
data、computed和methods。只有整个应用都要访问的数据才适合使用store,不要滥用strore
- Store使用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++
},
},
})
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 }
})
ref()就是state属性computed()就是gettersfunction()就是actions
注意: 要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性。这意味着,你不能在 store 中使用私有属性。
使用Store
使用
<script setup>调用useStore()(或者使用setup()函数,像所有的组件那样) 之前,store 实例是不会被创建的
<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useCounterStore()
</script>
Store解构
使用storeToRefs()对Store提取属性并保持响应式,和toRefs的区别在于storeToRefs会跳过所有的action以及非响应式的属性
<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
在Option Store中state是一个返回初始状态的函数,在setup Store中,你定义的每一项数据都是state
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 为了完整类型推理,推荐使用箭头函数
state: () => {
return {
// 所有这些属性都将自动推断出它们的类型
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
访问State
通过store实例直接访问
const store = useStore()
store.count++
重置State
选项式API通过$reset()方法将state重置为初始值
const store = useStore()
store.$reset()
组合式Setup中,需要自己创建重置方法
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
修改State
直接修改
countStore.sum = 666
批量修改$patch
countStore.$patch({
sum:999,
name:'测试'
})
通过action修改
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count', {
actions: {
//加
increment(value:number) {
if (this.sum < 10) {
//操作countStore中的sum
this.sum += value
}
},
//减
decrement(value:number){
if(this.sum > 1){
this.sum -= value
}
}
},
})
组件中调用action
// 使用countStore
const countStore = useCountStore()
// 调用对应action
countStore.incrementOdd(n.value)
订阅state($subscribe)
通过 store 的
$subscribe()方法侦听 state 及其变化。比起普通的watch(),使用$subscribe()的好处是 subscriptions 在 patch 后只触发一次
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// 和 cartStore.$id 一样
mutation.storeId // 'cart'
// 只有 mutation.type === 'patch object'的情况下才可用
mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('cart', JSON.stringify(state))
})
Getter
等同于 store 的 state 的计算值。可以通过
defineStore()中的getters属性来定义它们。推荐使用箭头函数,并且它将接收state作为第一个参数(有点类似计算属性)
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
})
注意: 可以通过 this 访问到整个 store 实例,但(在 TypeScript 中)必须定义返回类型。
Action
相当于组件中的method。它们可以通过
defineStore()中的actions属性来定义,并且它们也是定义业务逻辑的完美选择。
export const useCounterStore = defineStore('main', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
action可以是异步的,实际开发中可能通过async await 调用api接口之类的业务请求,并且可以通过this访问整个store实例
访问其他 store 的 action
将其他store引入,然后得到对应引入的store
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
preferences: null,
// ...
}),
actions: {
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
订阅 action($onAction)
通过
store.$onAction()来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。同样地,onError允许你在 action 抛出错误或 reject 时执行一个回调函数。
const unsubscribe = someStore.$onAction(
({
name, // action 名称
store, // store 实例,类似 `someStore`
args, // 传递给 action 的参数数组
after, // 在 action 返回或解决后的钩子
onError, // action 抛出或拒绝的钩子
}) => {
// 为这个特定的 action 调用提供一个共享变量
const startTime = Date.now()
// 这将在执行 "store "的 action 之前触发。
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 这将在 action 成功并完全运行后触发。
// 它等待着任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回一个拒绝的 promise,这将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动删除监听器
unsubscribe()
面试重点
Pinia 和 Vuex 的区别是什么?
- 取消
mutations,直接通过actions同步/异步修改状态 - 基于
Composition API设计,天然支持 TypeScript - 模块化无需嵌套,每个 Store 独立
- 更简洁的 API(
defineStore+ref/reactive)
Pinia 如何实现响应式?
- 底层使用
reactive()包裹 state - Getters 自动缓存计算结果(类似
computed)
如何优雅处理异步操作的错误?
在 Action 中统一捕获,try,catch进行错误捕获
在组件外使用 Pinia Store得应用场景
在路由守卫中判断用户是否登录,或者工具函数中进行状态处理