Vue3 状态管理之pinia
什么是Pinia
在vue3之前我们最常用的状态管理就是Vuex
,当然在vue3中也有相对应的Vuex
的版本。但是还是推荐使用Pinia
,原因有以下几点:
-
语法简单,mutations 不再存在。 -
无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的 -
不再需要注入、导入函数、调用函数、享受自动完成功能! -
无需动态添加 Store,默认情况下它们都是动态的 -
不再有 modules 的嵌套结构。 -
没有 命名空间模块。
Pinia
使用
安装
yarn add pinia
# 或者使用 npm
npm install pinia
创建pinia
在main.js
中创建pinia
并挂载。
import { createPinia } from 'pinia'
app.use(createPinia())
定义一个Store
在src/store/index.js
里引入pinia,并且导出。
Store 是使用 defineStore()
定义的,并且它需要一个唯一名称,作为第一个参数传递:
import { defineStore } from "pinia";
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
})
在页面中调用
import {useStore} from "../store/index.js";
const store = useStore()
console.log('store',store)
一旦 store
被实例化,你就可以直接在 store
上访问 state
、getters
和 actions
中定义的任何属性。
State
大多数时候,state
是 store
的核心部分。 我们通常从定义应用程序的状态开始。 在 Pinia
中,状态被定义为返回初始状态的函数。 Pinia
在服务器端和客户端都可以工作。
-
创建 state
import { defineStore } from "pinia";
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
// 定义一个是否展开的字段isCollapse
isCollapse:false,
counter: 0,
}
}
})
-
访问 state
<template>
<p>获取的值:{{store.counter}}</p>
</template>
<script setup>
import {useStore} from "../store/index.js";
const store = useStore()
console.log('store',store.counter)
</script>
-
重置状态 可以通过调用 store
上的$reset()
方法将状态 重置 到其初始值:
<template>
<p>获取的值:{{store.counter}}</p>
<button @click="reset">重置</button>
</template>
<script setup>
import {useStore} from "../store/index.js";
const store = useStore()
store.counter++
const reset = () => {
store.$reset()
}
</script>
-
改变状态 -- $patch
除了直接用 store.counter++
修改 store
,你还可以调用 $patch
方法。 它允许您使用部分“state
”对象同时应用多个更改:
<template>
<button @click="open">展开</button>
</template>
<script setup>
import {useStore} from "../store/index.js";
const store = useStore()
const open = () => {
store.$patch({
// 修改你要修改的数据
isCollapse: !store.isCollapse
})
}
</script>
或者
const open = () => {
store.$patch( (state) => {
state.isCollapse = !store.isCollapse
})
}
-
改变状态 -- actions
可以在pinia
中创建修改方法
import { defineStore } from "pinia";
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
// 定义一个是否展开的字段isCollapse
isCollapse: true,
counter: 0,
}
},
actions: {
updateCollapse() {
this.isCollapse = !this.isCollapse
}
}
})
在页面调用updateCollapse
方法,页面同上就省略多余代码
const open = () => {
store.updateCollapse()
}
-
订阅状态
以通过 store
的$subscribe()
方法查看状态及其变化,类似于 Vuex
的 subscribe
方法。 与常规的 watch()
相比,使用 $subscribe()
的优点是 subscriptions
只会在 patches
之后触发一次(例如,当使用上面的函数版本时)。
$subscribe
监听store
数据修改 当数据改变了 那么subscribe
也会触发。
<script setup>
import {useStore} from "../store/index.js";
const store = useStore()
const open = () => {
store.$patch({
// 修改你要修改的数据
isCollapse: !store.isCollapse
})
}
// 监听store数据修改
let subscribe = store.$subscribe( (params,state) => {
console.log('params',params)
console.log('state',state)
// 每当它发生变化时,将整个状态持久化到本地存储
localStorage.setItem('collapseState', state.isCollapse)
})
</<script>
getters
Getter
完全等同于 Store
状态的 计算值。 它们可以用defineStore()
中的 getters
属性定义。 他们接收“状态”作为第一个参数以鼓励箭头函数的使用:
import { defineStore } from "pinia";
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
// 定义一个是否展开的字段isCollapse
isCollapse: false,
counter: 1,
}
},
getters: {
// 自动将返回类型推断为数字
doubleCount: (state) => state.counter * 2,
},
actions: {
updateCollapse() {
this.isCollapse = !this.isCollapse
}
}
})
你可以直接在 store
实例上访问 getter
:
<p>Double count is {{ store.doubleCount }}</p>
-
访问其他 getter
与计算属性一样,您可以组合多个 getter
。 通过 this
访问任何其他 getter
。
import { defineStore } from "pinia";
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
// 定义一个是否展开的字段isCollapse
isCollapse: false,
counter: 1,
}
},
getters: {
// 自动将返回类型推断为数字
doubleCount: (state) => state.counter * 2,
doubleCountPlusOne() {
// 自动完成
return this.doubleCount + 1
},
},
actions: {
updateCollapse() {
this.isCollapse = !this.isCollapse
}
}
})
-
将参数传递给 getter
Getters
只是幕后的computed
属性,因此无法向它们传递任何参数。 但是,您可以从getter
返回一个函数以接受任何参数:
export const useStore = defineStore('main', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
在组价中使用
<script>
export default {
setup() {
const store = useStore()
return { getUserById: store.getUserById }
},
}
</script>
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>
-
访问其他 Store
的getter
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
Actions
Actions
相当于组件中的 methods
。 它们可以使用 defineStore()
中的 actions
属性定义,并且它们非常适合定义业务逻辑,下面来举个例子:
import { defineStore } from "pinia";
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main',{
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
// 定义一个是否展开的字段isCollapse
isCollapse: false,
counter: 1,
userData: null
}
},
getters: {
// 自动将返回类型推断为数字
doubleCount: (state) => state.counter * 2,
doubleCountPlusOne() {
// 自动完成
return this.doubleCount + 1
},
},
actions: {
updateCollapse() {
this.isCollapse = !this.isCollapse
},
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
// 异步调用
async registerUser(login,password) {
try {
this.userData = await api.post({ login, password})
console.log(`Welcome back`,this.userData.name)
} catch (error) {
return error
}
}
}
})
Actions
在页面上的调用:
import {useStore} from "../store/index.js";
const store = useStore()
const add = () => {
//store.increment()
store.randomizeCounter()
}
-
访问其他 store
操作
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
-
订阅 Actions
可以使用 store.$onAction()
订阅 action
及其结果。 传递给它的回调在 action
之前执行。 after
处理 Promise
并允许您在 action
完成后执行函数。
const unsubscribe = someStore.$onAction(
({
name, // action 的名字
store, // store 实例
args, // 调用这个 action 的参数
after, // 在这个 action 执行完毕之后,执行这个函数
onError, // 在这个 action 抛出异常的时候,执行这个函数
}) => {
// 记录开始的时间变量
const startTime = Date.now()
// 这将在 `store` 上的操作执行之前触发
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 如果 action 成功并且完全运行后,after 将触发。
// 它将等待任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回 Promise.reject ,onError 将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动移除订阅
unsubscribe()
pinia
模块化
pinia
中定义模块 只需要定义多个store
即可 因为pinia
没有单一数据源这个概念 在其中可以定义多个store
对象。
<script setup>
// 引用第一个store
import {useCommonStore} from "../store/common"
// 引用第2个store
import {useUserStore} from "../store/user"
let commonStore = useCommonStore()
let userStore = useUserStore()
</script>