Pinia 介绍
Pinia 起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。从那时起,我们就倾向于同时支持 Vue 2 和 Vue 3,并且不强制要求开发者使用组合式 API,我们的初心至今没有改变。除了安装和 SSR 两章之外,其余章节中提到的 API 均支持 Vue 2 和 Vue 3。虽然本文档主要是面向 Vue 3 的用户,但在必要时会标注出 Vue 2 的内容,因此 Vue 2 和 Vue 3 的用户都可以阅读本文档。
Pinia 优势
- 完整的 ts 的支持;
- 足够轻量,压缩后的体积只有1kb左右;
- 去除 mutations,只有 state,getters,actions;
- actions 支持
同步
和异步
; 代码扁平化没有模块嵌套
,只有 store 的概念,store 之间可以自由使用,每一个store都是独立的- 无需手动添加 store,store 一旦创建便会自动添加;
- 支持Vue3 和 Vue2
为什么要使用 Pinia:
Pinia 是 Vue 的存储库,它允许跨组件/页面共享状态
。
如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:
dev-tools 支持
跟踪动作、突变的时间线
Store 出现在使用它们的组件中
time travel 和 更容易的调试
热模块更换
在不重新加载页面的情况下修改您的 Store
在开发时保持任何现有状态
插件:使用插件扩展 Pinia 功能
为 JS 用户提供适当的 TypeScript 支持或 autocompletion
服务器端渲染支持
Pinia 安装和引入
起步 安装
yarn add pinia
npm install pinia
引入注册Vue3
import { createApp } from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
const store = createPinia()
let app = createApp(App)
app.use(store)
app.mount('#app')
Vue2 使用
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
// other options...
// ...
// note the same `pinia` instance can be used across multiple Vue apps on
// the same page
pinia,
})
Pinia 初始化仓库Store
1.新建一个文件夹Store
2.新建文件[name].ts
3.定义仓库Store
import { defineStore } from 'pinia'
4.我们需要知道存储是使用定义的defineStore(),并且它需要一个唯一的名称,作为第一个参数传递
这儿名称抽离出去了, 新建文件store-namespace/index.ts
export const enum Names {
Test = 'TEST'
}
store 引入
import { defineStore } from 'pinia'
import { Names } from './store-namespace'
export const useTestStore = defineStore(Names.Test, {
})
这个名称,也称为id,是必要的
,Pania 使用它来将商店连接到 devtools。将返回的函数命名为use…是可组合项之间的约定,以使其使用习惯。
-
- 定义值
State 箭头函数 返回一个对象 在对象里面定义值
import { defineStore } from 'pinia'
import { Names } from './store-namespce'
export const useTestStore = defineStore(Names.Test, {
state:()=>{
return {
current:1
}
}
})
import { defineStore } from 'pinia'
import { Names } from './store-namespce'
export const useTestStore = defineStore(Names.Test, {
state:()=>{
return {
current:1
}
},
//类似于computed 可以帮我们去修饰我们的值
getters:{
},
//可以操作异步 和 同步提交state
actions:{
}
})
修改 State
1.State 是允许直接修改值的
例如current++
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.current++ // 注意这里
}
</script>
VUEX 不允许如此修改,必须通过mutation提交
,pinia可以直接修改
2.批量修改State的值 – $patch
实例上有$patch方法可以批量修改多个值
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
<div>
{{Test.age}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.$patch({ // 注意这里
current:200,
age:300
})
}
</script>
3.批量修改函数形式
推荐使用函数形式 可以自定义修改逻辑
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
<div>
{{Test.age}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.$patch((state)=>{ // 注意这里 .$patch
state.current++;
state.age = 40
})
}
</script>
4.通过原始对象修改整个实例
$state
您可以通过将store的属性设置为新对象来替换store的整个状态
缺点就是 必须修改整个对象的所有属性
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
<div>
{{Test.age}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.$state = { // 注意这里 .$state
current:10,
age:30
}
}
</script>
5.通过actions
修改
定义Actions
在actions 中直接使用this就可以指到state里面的值
import { defineStore } from 'pinia'
import { Names } from './store-naspace'
export const useTestStore = defineStore(Names.TEST, {
state:()=>{
return {
current:1,
age:30
}
},
actions:{ // 注意这里 setCurrent
setCurrent () { // 这里不要写箭头函数,否则this指向会错误
this.current++
}
}
})
使用方法直接在实例调用
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.current}}
</div>
<div>
{{Test.age}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.setCurrent() // 注意这里 setCurrent
}
</script>
解构store
在Pinia是不允许直接解构,是会失去响应性的
const Test = useTestStore()
const { current, name } = Test
console.log(current, name);
差异对比
修改Test current 解构完之后的数据不会变
而源数据是会变的
<template>
<div>origin value {{Test.current}}</div>
<div>
pinia:{{ current }}--{{ name }}
change :
<button @click="change">change</button>
</div>
</template>
<script setup lang='ts'>
import { useTestStore } from './store'
const Test = useTestStore()
const change = () => {
Test.current++
}
const { current, name } = Test
console.log(current, name);
</script>
</style>
解决方案可以使用 storeToRefs
import { storeToRefs } from 'pinia'
const Test = useTestStore()
const { current, name } = storeToRefs(Test) // storeToRefs
其原理跟toRefs 一样的给里面的数据包裹一层toref
源码 通过toRaw使store变回原始数据防止重复代理
循环store 通过 isRef isReactive 判断 如果是响应式对象直接拷贝一份给refs 对象 将其原始对象包裹toRef 使其变为响应式对象
Actions,getters
Actions(支持同步异步)
1.同步 直接调用即可
import { defineStore } from 'pinia'
import { Names } from './store-naspace'
export const useTestStore = defineStore(Names.TEST, {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})
<template>
<div>
<button @click="Add">+</button>
<div>
{{Test.counter}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.randomizeCounter()
}
</script>
2.异步 可以结合async await 修饰
import { defineStore } from 'pinia'
import { Names } from './store-naspace'
type Result = {
name: string
isChu: boolean
}
const Login = (): Promise<Result> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: '小',
isChu: true
})
}, 3000)
})
}
export const useTestStore = defineStore(Names.TEST, {
state: () => ({
user: <Result>{},
name: "123"
}),
actions: {
async getLoginInfo() {
const result = await Login()
this.user = result;
}
},
})
template
<template>
<div>
<button @click="Add">test</button>
<div>
{{Test.user}}
</div>
</div>
</template>
<script setup lang='ts'>
import {useTestStore} from './store'
const Test = useTestStore()
const Add = () => {
Test.getLoginInfo()
}
</script>
3.多个action互相调用getLoginInfo setName
state: () => ({
user: <Result>{},
name: "default"
}),
actions: {
async getLoginInfo() {
const result = await Login()
this.user = result;
this.setName(result.name) // 这里
},
setName (name:string) {
this.name = name;
}
},
getters
1.使用箭头函数不能使用this
, this指向已经改变指向undefined 修改值请用state
主要作用类似于computed 数据修饰并且有缓存
getters:{
newPrice:(state)=> `$${state.user.price}`
},
2.普通函数形式可以使用this
getters:{
newCurrent ():number {
return ++this.current
}
},
3.getters 互相调用
getters:{
newCurrent ():number | string {
return ++this.current + this.newName
},
newName ():string {
return `$-${this.name}`
}
},
API
1.$reset
重置store到他的初始状态
state: () => ({
user: <Result>{},
name: "default",
current:1
}),
Vue 例如我把值改变到了10
const change = () => {
Test.current++
}
调用$reset(); 将会把state所有值 重置回 原始状态
2.订阅state的改变
类似于Vuex 的abscribe 只要有state 的变化就会走这个函数
Test.$subscribe((args,state)=>{
console.log(args,state);
})
返回值
第二个参数 如果你的组件卸载之后还想继续调用请设置第二个参数
Test.$subscribe((args,state)=>{
console.log(args,state);
},{
detached:true
})
3.订阅Actions的调用
只要有actions被调用就会走这个函数
Test.$onAction((args)=>{
console.log(args);
})
pinia数据持久化
pinia和vuex都有一个通病 – 页面刷新状态会丢失
。 就是说数据持久化需要手动修改
,插件本身不具备数据持久化的能力,当页面刷新或者应用更新后所有的状态数据均会丢失。
pinia是支持插件的,不知道各位小伙伴看过没有,这里引用的pinia数据持久化插件是pinia-plugin-persistedstate
。
安装:pinia-plugin-persistedstate
pnpm : pnpm i pinia-plugin-persistedstate
npm : npm i pinia-plugin-persistedstate
yarn : yarn add pinia-plugin-persistedstate
配置插件pinia-plugin-persistedstate
在src目录下新增store文件夹,新增index.js
// pinia数据持久化存储
import { createPinia } from "pinia"
import { createPersistedState } from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(createPersistedState({
serializer: { // 指定参数序列化器
serialize: JSON.stringify,
deserialize: JSON.parse,
}
}))
export default store
在src新增store文件夹下,新增main.ts
import { defineStore } from "pinia"
export const useMainStore = defineStore({
id: 'main',
state: () => ({
name: 'hello pinia'
}),
// persist: true, 开启数据持久化,所有store数据将被持久化到指定仓库
persist: { // 自定义数据持久化方式
// key: 'store-key', 指定key进行存储,此时非key的值不会持久化,刷新就会丢失
storage: window.localStorage, // 指定换成地址
// paths: ['nested.data'],// 指定需要持久化的state的路径名称
beforeRestore: context => {
console.log('Before' + context)
},
afterRestore: context => {
console.log('After'+ context)
},
},
getters: {
getName: (state) => {
return state.name
}
},
actions: {
SET_NAME (param:string) {
this.name = param
}
}
})
再将store下的index.ts引入到入口文件main.ts文件里
import { createApp } from 'vue'
import App from './App.vue'
// 注入路由
import router from './router'
// 注入store
import store from './store'
import 'element-plus/dist/index.css'
// 导入element-plus消息api
import { ElMessage } from "element-plus"
import { currentTiemStr } from "@/script/utils"
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
// 全局常用api配置
// 消息提示APi
app.config.globalProperties.$ElMessage = ElMessage
// 获取时间戳方法
app.config.globalProperties.$CurrentTiemStr = currentTiemStr
使用方法还是同上述1是一样的,当变化name值后,你刷新数据,会发现便后的name值一直都是最新值,至此,pinia使用插件pinia-plugin-persistedstate
pinia插件
由于有了底层 API 的支持,Pinia store 现在完全支持扩展。以下是你可以扩展的内容:
- 为 store 添加新的属性
- 定义 store 时增加新的选项
- 为 store 增加新的方法
- 包装现有的方法
- 改变甚至取消 action
- 实现副作用,如本地存储
- 仅应用插件于特定 store
插件是通过 pinia.use()
添加到 pinia 实例的。最简单的例子是通过返回一个对象将一个静态属性添加到所有 store。
import { createPinia } from 'pinia'
// 在安装此插件后创建的每个 store 中都会添加一个名为 `secret` 的属性。
// 插件可以保存在不同的文件中
function SecretPiniaPlugin() {
return { secret: 'the cake is a lie' }
}
const pinia = createPinia()
// 将该插件交给 Pinia
pinia.use(SecretPiniaPlugin)
// 在另一个文件中
const store = useStore()
store.secret // 'the cake is a lie'
const __piniaKey = '__PINIAKEY__'
//定义兜底变量
type Options = {
key ? : string
}
//定义入参类型
//将数据存在本地
const setStorage = (key: string, value: any): void => {
localStorage.setItem(key, JSON.stringify(value))
}
//存缓存中读取
const getStorage = (key: string) => {
return (localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key) as string) : {})
}
//利用函数柯丽华接受用户入参
const piniaPlugin = (options: Options) => {
//将函数返回给pinia 让pinia 调用 注入 context
return (context: PiniaPluginContext) => {
const {
store
} = context;
const data = getStorage(`${options?.key ?? __piniaKey}-${store.$id}`)
store.$subscribe(() => {
setStorage(`${options?.key ?? __piniaKey}-${store.$id}`, toRaw(store.$state));
})
//返回值覆盖pinia 原始值
return {
...data
}
}
}
//初始化pinia
const pinia = createPinia()
//注册pinia 插件
pinia.use(piniaPlugin({
key: "pinia"
}))
参考地址:
- https://blog.csdn.net/qq1195566313/category_11672479.html