Pinia
Pinia简称小菠萝 🍍,是一个专为 Vue 3 设计的现代化状态管理库,为Vue 3开发的,它提供了一种简单、可扩展和类型安全的方式来管理应用程序的状态。
与Vue 2中的 Vuex 相比,Pinia提供了更好的TypeScript 支持,具有更好的类型定义和类型推断,可在编译时捕获错误,提供更高的代码可靠性和开发体验。它是专为Vue 3设计的,充分利用了Vue 3的新特性,如Composition API,以提供更直接、自然和灵活的状态管理体验。Pinia的核心概念是Store,它类似于Vuex中的模块,用于管理应用程序的状,可以将相关的状态和逻辑组合到单个Store中,使代码更清晰、结构更有组织性。除此之外海提供了许多有用的特性和功能,例如模块化组织、状态持久化、插件扩展等。
总的来说,Pinia是一个功能强大而灵活的状态管理解决方案,适用于各种规模的Vue 3应用程序。它提供了现代化的特性和工具,帮助我们更好地组织、管理和扩展应用程序的状态,同时提供了更好的类型安全和开发体验。
一、安装
运行安装命令
npm install pinia在main.ts中引入
// main.ts
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
新建stores文件,用于存放所有的store,然后创建index.ts。
同过 defineStore() 定义一个store,它接受一个参数作为仓库名称,也就是Id。它返回一个函数,默认我们使用user开头的风格来接收。第二个参数为一个Setup函数或者Option对象。
import { defineStore } from 'pinia';
export const useUsersStore = defineStore('users', {
// 其他配置...
});1.Option Store
这种方式熟悉Vuex的很了解,传入一个带有 state、actions 与 getters 属性的 Option 对象
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100
}
},
getters: {
getName: (state) => state.name + '🐔你好帅'
},
actions:{
getUserInfo {
...
}
}
})在 Option Store 中:
state是store的数据datagetters是store的计算属性computedactions则是方法methods
2.Setup Store
和Vue3 Composition API组合式API里setup函数相似,传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想要暴露出去的属性和方法的对象。
export const userUsersStore = defineStore('users', () => {
const name = ref('inkun');
function getInkun() {
getInkun.value + '🐔你好帅';
}
return { name, getInkun };
});在 Setup Store 中:
ref()就是state属性computed()就是gettersfunction()就是actions
3.使用 Store
定义一个store后,在组件里引入这个store然后就行使用,不需要像ref一样使用.value,可以直接修改访问。
<script setup lang="ts">
import {useCounterStore} from '@/stores/counter' // 可以在组件中的任意位置访问 `store` 变量 ✨ const store =
useCounterStore()
</script>三、State
state定义一个返回初始状态的函数,函数内返回一个对象,里面是需要定义的数据。
对于基础类型而言,[[../TypeScript|TypeScript]]可以自行推断出它们的数据类型,也可以接口,定义state函数返回值。
interface State {
userList: UserInfo[];
user: UserInfo | null;
}
interface UserInfo {
name: string;
age: number;
}
export const userUsersStore = defineStore('users', {
state: (): State => {
return {
userList: [],
user: null,
};
},
});1.修改 State
默认情况下可以直接通过store实例访问state,并且可以直接对其进行读写操作。
在Vuex中,如果要对state进行修改必须要定一个mutation,通过mutation进行提交,太过于繁琐。
const store = useStore();
store.count++;变更
除了用 store.count++ 直接改变 store,还可以调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性:
store.$patch({
count: store.count + 1,
name: 'ff',
});重置
可以通过调用 store 的 $reset() 方法将 state 重置为初始值。
const store = useStore();
store.$reset();监听
类似于 Vuex 的 subscribe 方法,可以通过 store 的 $subscribe() 方法侦听 state 及其变化。
store.$subscribe(
(mutation, state) => {
mutation.storeId; // 'cart'
console.log('state change', state);
console.log('mutation', mutation.type); // 'direct' | 'patch object' | 'patch function'
console.log('mutation2', mutation.storeId); // 'users'
// 只有 mutation.type === 'patch object'的情况下才可用
// mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
console.log('mutation3', mutation.payload);
},
{
detached: true,
}
);默认情况下,state subscription 会被绑定到添加它们的组件上,当该组件被卸载时,它们将被自动删除。如果想在组件卸载后依旧保留它们,将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离,此时组件卸载后,订阅器还可以使用。
2.结构 State
在使用state时是不允许直接从store中结构数据,这样会导致数据失去响应式和props一样。
解构出来的数据是可以正常访问,当数据修改时是不会发生任何变化。
<script setup lang="ts">
import {useCounterStore} from '@/stores/counter' const {(current, name)} = useCounterStore() // 数据不会发生变化
function change() {store.current++}
</script>解决方案是通过storeToRefs将数据重新变回响应式。
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const store= useCounterStore() // 数据不会发生变化
const {name, current} = storeToRefs(store)
function change() {
store.current = 1
name.value = 'ff'
}
</script>四、Getter
getter相当于计算属性,接收一个函数,函数参数为当前store里的state,也可以通过this去访问。
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100,
};
},
getUserName(state) {
return state.name + '🐔你好帅';
},
getName(): string {
return this.name + '🐔你实在太帅';
},
});然后就可以通过store实例访问getter
<template> {{ store.getUserName }} {{ store.getName }} </template>
<script setup lang="ts">
import { userUsersStore } from './stores';
const store = userUsersStore();
</script>1.访问其他 Getter
通过this可以访问其他的getter
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100,
};
},
getUserName(state) {
return '大家好,我是' + state.name;
},
getName(): string {
return this.getUserName + '🐔你实在太帅';
},
});2.向 Getter 传递参数
getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,可以从 getter 返回一个函数,该函数可以接受任意参数:
export const userUsersStore = defineStore('users', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>3.访问其他 Store 里的 Getter
将要访问的store引入并实例就可以
import { useOtherStore } from './other-store';
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore();
return state.localData + otherStore.data;
},
},
});五、action
action相当于method,和Vuex不同的是它异步同步都可以定义。
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100
}
},
actions:{
async getUserInfo {
...
}
}
})和getter一样,也可以通过this访问state数据
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 100,
};
},
actions: {
randomizeCounter() {
this.count = Math.round(100 * Math.random());
},
},
});在模版上也是和其他一样通过store直接访问。
<template>
<button type="button" @click="getUserInfo">获取</button>
</template>监听
可以通过store.$onAction()来监听 action 和它们的结果。第一个参数为回调函数,可以获取action 的一些信息,第二个参数如果想在组件卸载后依旧保留它们,将 true作为第二个参数传递给action` 订阅器。
它返回一个函数,可以在必要的时候调用函数,此时会删除订阅器取消监听。
<script setup lang="ts">
import { userUsersStore } from './stores'
const store = userUsersStore()
const unsubscribe = store.$onAction(({ name, store, args, after, onError }) => {})
// 取消监听
unsubscribe()
</script>。六、数据持久化
和Vuex一样,都存在刷新后数据就会丢失,可以通过pinia-plugin-persistedstate插件来解决。
通过在将数据存储到本地storage中,避免数据刷新丢失。储存位子有两个一个是LocalStorage和SessionStorage,具体看个人情况使用。
针对存储的位置,在使用的时候需要考虑项目是否真的要存储在某个位置,合理使用。不能说将用户头像、名称等信息存储在SessionStorage中,网站关闭后数据也还是会丢失。也不能说将IM聊天室消息、所有用户信息等数据存储在LocalStorage中,存储的大小也有限制,这是时候就要使用IndexDB、web SQL等方式。所以需要结合项目功能情况。合理选择存储,而不是一股脑的使用。
1.安装
npm i pinia-plugin-persistedstate将插件添加到pinia实例上
// main.ts
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);2.使用
在创建store时,设置persist: true
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 1
}
},
getters: {
...
},
actions: {
...
},
persist: true
})设置完后可以在网页中看到数据存储在localStorage中
3.配置 persist
persist可以接收一个对象
export const userUsersStore = defineStore('users', {
state: () => {
return {
name: 'inkun',
current: 1,
};
},
persist: {
key: 'my-custom-key',
storage: sessionStorage,
paths: ['current'],
serializer: {
deserialize: parse,
serialize: stringify,
},
beforeRestore: (ctx) => {
console.log(`即将恢复 '${ctx.store.$id}'`);
},
afterRestore: (ctx) => {
console.log(`刚刚恢复完 '${ctx.store.$id}'`);
},
},
});key: 用于引用storage中的数据,默认使用store中的Idstorage:数据存储位置,默认localStorage,可以该为sessionStoragepaths:指定state中哪些数据需要持久化serializer:指定持久化时所使用的序列化方法,以及恢复store时的反序列化方法。beforeRestore:该hook将在从storage中恢复数据之前触发,并且它可以访问整个PiniaPluginContext,这可用于在恢复数据之前强制地执行特定的操作。afterRestore:该hook将在从storage中恢复数据之后触发,并且它可以访问整个PiniaPluginContext,这可用于在恢复数据之后强制地执行特定的操作。
4.全局配置
使用全局配置,就不用单独在每个store里面做配置,在使用pinia use的时候就可以通过createPersistedState函数设置。
// main.ts
import { createPinia } from 'pinia';
import { createPersistedState } from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(
createPersistedState({
storage: sessionStorage,
paths: ['current'],
})
);createPersistedState里的配置会将每个申明persist: true的store添加上配置,但是每个单独store里的配置将会覆盖调全局声明中的对应项。
全局配置支持一下属性:
- storage
- serializer
- beforeRestore
- afterRestore
5.启用所有 Store 默认持久化
该配置将会使所有 store 持久化存储,且必须配置 persist: false 显式禁用持久化。
import { createPinia } from 'pinia';
import { createPersistedState } from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(
createPersistedState({
auto: true,
})
);6.Store 多个持久化配置
在一些特殊情况下,每个store中的数据存储的位置不一样,可以将persist设置为接收多个配置形式。
import { defineStore } from 'pinia';
defineStore('store', {
state: () => ({
toLocal: '',
toSession: '',
toNowhere: '',
}),
persist: [
{
paths: ['toLocal'],
storage: localStorage,
},
{
paths: ['toSession'],
storage: sessionStorage,
},
],
});7.强制恢复数据
每个 store 都有 $hydrate 方法来手动触发数据恢复。默认情况下,调用此方法还将触发 beforeRestore 和 afterRestore 钩子。但是可以通过配置方法来避免这两个钩子触发。
import { defineStore } from 'pinia';
const useStore = defineStore('store', {
state: () => ({
someData: '你好 Pinia',
}),
});调用 $hydrate 方法:
const store = useStore();
store.$hydrate({ runHooks: false });这将从 storage 中获取数据并用它替换当前的 state。并且在上面的示例中,配置了runHooks: false,所以 beforeRestore 和 afterRestore 钩子函数不会被触发。
8.强制持久化
除了通过persist方式设置持久化,每个store都有$persist方法来手动触发持久化,这会强制将 store state 保存在已配置的 storage 中。
import { defineStore } from 'pinia';
const useStore = defineStore('store', {
state: () => ({
someData: '你好 Pinia',
}),
});
// App.vue
const store = useStore();
store.$persist();