Pinia
Pinia
是 Vue
的存储库,它允许您跨组件/页面共享状态,是 VueX
的代替者。
Vue2
和Vue3
都支持;- 只有
state
、getter
、action
属性,抛弃了传统的Mutation
; - 符合
Vue3
的Composition api
,支持TypeScript
,有很好的代码提示。
中文文档:https://pinia.web3doc.top/
安装
yarn add pinia
# 或者使用 npm
npm install pinia
注册
- 创建
store
文件夹,新建index.ts
文件
// /src/store/index.ts
import { createPinia } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';
const pinia = createPinia();
pinia.use(piniaPluginPersist);
export default pinia;
main.ts
中使用
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import pinia from '@/store/index';
createApp(App).use(pinia).mount('#app')
创建
选项式写法
定义store
import { defineStore } from 'pinia'
/*
第一个参数是应用程序中 store 的唯一 id
第二个参数是个对象
*/
export const useStore = defineStore('useStore', {
// other options...
})
state
存储数据的地方。在 Pinia
中,状态被定义为返回初始状态的函数。
state
中的状态尽量不要直接修改,而是通过 actions
中的函数进行更新。
enum UserEnum {
NAME = 'name',
AGE = 'age'
}
interface UserStore {
[UserEnum.NAME]: string
[UserEnum.AGE]: number
}
export const useStore = defineStore('useStore', {
// 写法1
/* state: (): UserStore => {
return {
name: '张三',
age: 18
}
}, */
// 写法2
state: (): UserStore => ({
name: '张三',
age: 18
}),
})
getters
Getter
完全等同于Store
状态的计算值- 同
computed
属性,也具有缓存的特性,值不改变就不会重新执行 - 可以在定义常规函数时通过
this
访问到 整个 store 的实例, 但是需要定义返回类型(在TypeScript
中) - 可以从 getter 返回一个函数以接受任何参数
export const useStore = defineStore('useStore', {
state: (): UserStore => ({
name: '张三',
age: 18
}),
getters: {
getNameByThis(): string {
return this.name
},
getNameByState: (state): string => state.name,
// 接收参数
getValueByKey: (state) => (key: UserEnum) => state[key],
}
})
actions
Actions
相当于组件中的methods
- 适合定义业务逻辑,可以是异步的
- 可以访问其他
store
操作 - 更新
state
的操作最好在Actions
执行
import { useInfoStore } from '../useInfo'
export const useStore = defineStore('useStore', {
state: (): UserStore => ({
name: '张三',
age: 18
}),
getters: {
getNameByThis(): string {
return this.name
},
getNameByState: (state): string => state.name,
getValueByKey: (state) => (key: UserEnum) => state[key],
},
actions: {
updateState() {
// 直接更新
// this.age = age
/* this.$patch((state) => {
state.age = state.age++
}) */
// 批量更新
this.$patch({
age: ++this.age,
name: '张三' + Math.random().toFixed(2)
})
},
// 异步更新
async asyncUpdate() {
// 访问其他store, 这个store的内容在下方的组合式写法中
const infoStore = useInfoStore()
try {
await new Promise((resolve, reject) => {
if (infoStore.info.success) {
setTimeout(() => {
resolve('操作成功!');
}, 1500)
} else {
reject(new Error('操作失败!'));
}
});
this.updateState()
} catch(e) {
console.log(e);
}
}
}
})
组合式写法
Composition API
中的ref
,reactive
定义的变量 等价于options API
中的state
Composition API
中的computed
属性 等价于options API
中的getters
Composition API
中的function函数
属性 等价于options API
中的actions
composition API
模式下不支持pinia store
内置方法。例如$reset()
,可以自己重写reset
函数。
import { ref, reactive, computed } from 'vue';
import { defineStore } from 'pinia';
// 使用composition API模式定义store
export const useInfoStore = defineStore('useInfoStore', () => {
const name = ref<string>('李四');
const age = ref<number>(18);
const info = reactive({
name: '李四',
age: 18
})
const getName = computed(() => name.value)
const getValueByKey = (key: keyof typeof info) => info[key]
const updateState = () => {
age.value++;
name.value = '王五';
Object.assign(info, {
name: '王五',
age: ++info.age
})
}
const asyncUpdate = async () => {
try {
await new Promise((resolve, reject) => {
const success = true;
if (success) {
setTimeout(() => {
resolve('操作成功!');
}, 1500)
} else {
reject(new Error('操作失败!'));
}
});
updateState()
} catch(e) {
console.log(e);
}
}
return { name, age, info, getName, getValueByKey, updateState, asyncUpdate };
});
组件中使用
解构
<!-- Child.vue -->
<template>
<div>
<h2>Child</h2>
<div>{{ name }}</div>
<div>{{ age }}</div>
<div>{{ infoStore.info }}</div>
</div>
</template>
<script setup lang="ts">
import { useInfoStore } from '../store/useInfo'
import { storeToRefs } from 'pinia'
const infoStore = useInfoStore()
// 直接解构会丢失响应式
// const { name, age } = infoStore
// 使用storeToRefs解构, 保留响应式
const { name, age } = storeToRefs(infoStore)
</script>
搭配computed
<!-- App.vue -->
<template>
<div>
<button @click="handleUpdate">更新</button>
<button @click="handleAsyncUpdate">异步更新</button>
</div>
<!-- 通过sotre[变量名]的方式使用, 是响应式的 -->
<div>{{ store.name }}</div>
<div>{{ store.age }}</div>
<!-- 搭配computed -->
<div>{{ info }}</div>
<div>{{ age }}</div>
<Child />
</template>
<script setup lang="ts">
import Child from './component/Child.vue';
import { useStore } from './store/useUser'
import { useInfoStore } from './store/useInfo'
// 注册store拿到整个仓库的变量
const store = useStore()
const infoStore = useInfoStore()
// 搭配computed使用,也可以做到响应式
const info = computed(() => infoStore.info)
// 调用getters属性
const age = computed(() => infoStore.getValueByKey('age'))
const handleUpdate = () => {
// 调用store方法
store.updateState()
infoStore.updateState()
}
const handleAsyncUpdate = () => {
store.asyncUpdate()
infoStore.asyncUpdate()
}
</script>
ts文件中使用
某些时刻需要在一些 utils
文件 或者 路由
文件中使用 store
,这个时候 pinia
还没初始化注册,此时就需要另外一种写法了。
// 引入导出的pinia对象
import pinia from '@/store/index';
// 引入需要使用的 store 对象
import { useLayoutConfig } from '/@/stores/useThemeConfig'
// 将pinia传入store,获取store对象数据
const layoutConfigStore = useLayoutConfig(pinia);
// 使用 layoutConfigStore[key]
数据持久化
sessionStorage
在更新 state
的操作后面, 直接将store的数据手动存到 sessionStorage
中,随后创建 store
时判断缓存里面是否有数据,有就读取缓存,没有就使用初始化默认值。
- 对
JSON
序列化另作封装,解决function
和undefined / null
丢失的问题,也可以直接使用js
库封装好的 - 存取
sessionStorage
也可以另作封装,判断是否是正确的json
格式字符串,自动序列化 - 初始化获取缓存值
import { defineStore } from 'pinia'
import { merge } from 'lodash'
enum UserEnum {
NAME = 'name',
AGE = 'age'
}
interface UserStore {
[UserEnum.NAME]: string
[UserEnum.AGE]: number
}
const defaultState: UserStore = {
name: '张三',
age: 18
}
const USER_STORE_KEY = 'userStore'
const initState = () => {
/*
这里需要判断是否能够获取到sessionStorage中的数据
随后判断 能否被正常 JSON 解析序列化成对象
--- 这里示例不做展示,根据实际业务需求进行处理即可
*/
const storageState = sessionStorage.get(USER_STORE_KEY)
// 使用lodash的merge深度合并
return storageState ? merge({}, defaultState, storageState) : defaultState
// 使用Object.assign合并覆盖
// return storageState ? Object.assign({}, defaultState, storageState) : defaultState
}
export const useStore = defineStore('useStore', {
// 初始化获取值
state: (): UserStore => (initState()),
getters: {
// ...getters
},
actions: {
// ...actions,
saveStroage() {
// 保存到本地 --- 序列化需做详细处理
sessionStorage.setItem(USER_STORE_KEY, JSON.stringify(this.$state))
},
setState<T extends keyof UserStore, K extends UserStore[T]>(key: T, value: K): void {
// 更新state操作
this.$patch(state => {
state[key] = value
})
// 执行更新操作后, 将state保存到本地
this.saveStroage()
}
}
})
- 也可以直接监听
pinia.state
import pinia from './store/index'
import { watch } from 'vue'
watch(() => pinia.state, (allStore) => {
// 这里可以看到所有注册的store对象,自行做缓存处理
console.log(allStore.value);
}, { deep: true })
pinia-plugin-persistedstate
插件 pinia-plugin-persistedstate
可以辅助实现数据持久化功能。
- 安装依赖
# 安装依赖
yarn add pinia-plugin-persistedstate
# 或者使用 npm
npm install pinia-plugin-persistedstate
# 或者使用 pnpm
pnpm add pinia-plugin-persistedstate
- 使用依赖
// /src/store/index.ts
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
// 创建
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
// 导出, 在main,ts中use
export default pinia;
- 模块开启持久化
export const useStore = defineStore('useStore', {
// ...otherOptions,
persist: true
})
- 缓存特定的数据
文档地址
:https://prazdevs.github.io/pinia-plugin-persistedstate/guide/advanced.html
export const useStore = defineStore('main', s{
state: () => {
return {
count: 0,
info: {
name: '张三',
age: 18
}
}
},
// 持久化存储插件其他配置
persist: {
// 修改存储中使用的键名称,默认为当前 Store的 id
key: 'storekey',
// 默认为localStorage, 修改为 sessionStorage,
storage: window.sessionStorage,
// 部分持久化状态的点符号路径数组,[]意味着没有状态被持久化(默认为undefined,持久化整个状态)
paths: ['nested.data'],
},
})
- 组合式持久化写法
插件文档
: https://prazdevs.github.io/pinia-plugin-persistedstate/guide/#usage
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useStore = defineStore(
'main',
() => {
const someState = ref('hello pinia')
return { someState }
},
{
persist: true,
},
)