一、Pinia 简介
- Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。
- Pinia 起源于一次探索 Vuex 下一个迭代的实验,因此结合了 Vuex 5 核心团队讨论中的许多想法。
mutation
已被弃用。它们经常被认为是极其冗余的。它们初衷是带来 devtools 的集成方案,但这已不再是一个问题了。
Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的。
- 极致轻量化:Pinia 大小只有 1kb 左右
- 开发工具支持:不管是 Vue 2 还是 Vue 3,支持 Vue devtools 钩子的 Pinia 都能给你更好的开发体验。
- 类型安全:类型可自动推断,即使在 JavaScript 中亦可为你提供自动补全功能!
- Pinia 起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。
- 从那时起,就倾向于同时支持 Vue 2 和 Vue 3,并且不强制要求开发者使用组合式 API。
二、基础示例
- 下面就是 pinia API 的基本用法
- 你可以先创建一个 Store:
import {
defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0 }
},
actions: {
increment() {
this.count++
},
},
})
- 为实现更多高级用法,你甚至可以使用一个函数(与组件 setup() 类似)来定义一个 Store:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return {
count, increment }
})
import {
useCounterStore } from '@/stores/counter'
export default {
setup() {
const counter = useCounterStore()
counter.count++
counter.$patch({
count: counter.count + 1 })
counter.increment()
},
}
- 如果你还不熟悉 setup() 函数和组合式 API,Pinia 也提供了一组类似 Vuex 的 映射 state 的辅助函数,通过 mapStores()、mapState() 或 mapActions() 访问:
export default {
computed: {
...mapStores(useCounterStore, useUserStore)
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
...mapActions(useCounterStore, ['increment']),
},
}
import {
useCounterStore } from '@/stores/counter'
function xxxx = () =>{
const counter = useCounterStore()
counter.count++
counter.increment()
}
三、核心概念
1、定义 Store
- 我们得知道 Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字:
import {
defineStore } from 'pinia'
export const useStore = defineStore('main', {
})
- 这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。
defineStore()
的第二个参数可接受两类值:Setup 函数或 Option 对象。
- 与 Vue 的选项式 API 类似,我们也可以传入一个带有 state、actions 与 getters 属性的 Option 对象:
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
- 与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return {
count, increment }
})
- 在 Setup Store 中:
ref() 就是 state 属性、computed() 就是 getters、function() 就是 actions
- Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。
2、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,
}
},
})
默认情况下,你可以通过 store 实例访问 state,直接对其进行读写。
const store = useStore()
store.count++
可以通过调用 store 的 $reset() 方法将 state 重置为初始值。
const store = useStore()
store.$reset()
除了用 store.count++ 直接改变 store,你还可以调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性:
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。
store.$patch((state) => {
state.items.push({
name: 'shoes', quantity: 1 })
state.hasChanged = true
})
你不能完全替换掉 store 的 state,因为那样会破坏其响应性。但是,你可以 patch 它。
store.$state = {
count: 24 }
store.$patch({
count: 24 })
3、Getter
- Getter 完全等同于 store 的 state 的计算值。
- 可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:
export const useStore = defineStore('main', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
})
- 大多数时候,getter 仅依赖 state,不过,有时它们也可能会使用其他 getter。
- 因此,即使在使用常规函数定义 getter 时,我们也可以通过 this 访问到整个 store 实例
export const useStore = defineStore('main', {
state: () => ({
count: 0,
}),
getters: {
doubleCount(state) {
return state.count * 2
},
doublePlusOne(): number {
return this.doubleCount + 1
},
},
})
- 然后你可以直接访问 store 实例上的 getter 了:
<template>
<p>Double count is {
{
store.doubleCount }}</p>
</template>
<script>
export default {
setup() {
const store = useStore()
return {
store }
},
}
</script>
4、Action
- Action 相当于组件中的 method。
- 它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。
export const useStore = 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!
import {
mande } from 'mande'
const api = mande('/api/users')
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({
login, password })
showTooltip(`Welcome back ${
this.userData.name}!`)
} catch (error) {
showTooltip(error)
return error
}
},
},
})
export default defineComponent({
setup() {
const main = useMainStore()
main.randomizeCounter()
return {
}
},
})
- 想要使用另一个 store 的话,那你直接在 action 中调用就好了:
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')
}
},
},
})