南京网站设计建设网站服务器
状态管理Pinia使用详解(带你从入门到入神)
序:
如果你之前使用过 vuex 进行状态管理的话,那么 pinia 就是一个类似的插件。它是最新一代的轻量级状态管理插件。你可以通过defineStore来简单创建一个存储管理。
与 vuex 相比,pinia 提供了一个更简单的 API,具有更少的操作,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。
特点:
- 完整的 ts 的支持;
- 足够轻量,压缩后的体积只有1kb左右;
- 去除 mutations,只有 state,getters,actions;
- actions 支持同步和异步;
- 代码扁平化没有模块嵌套,只有 store 的概念,store 之间可以自由使用,每一个store都是独立的
- 无需手动添加 store,store 一旦创建便会自动添加;
- 支持Vue3 和 Vue2
pinia官网
1.安装
yarn add pinianpm install pinia
2.引入注册Vue3
import { createApp } from 'vue'
import { createPinia } from 'pinia'import App from './App.vue'onst app = createApp(App)app.use(createPinia())app.mount('#app')
3.初始化仓库Store
3.1.新建一个文件夹Store
3.2.新建文件[name].ts
3.3.定义仓库Store
3.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…*是可组合项之间的约定,以使其使用习惯。
3.5.定义值
State 箭头函数 返回一个对象 在对象里面定义值
import { defineStore } from 'pinia'
import { Names } from './store-namespce'export const useTestStore = defineStore(Names.Test, {state:()=>{return {current:1}},//类似于computed 可以帮我们去修饰我们的值getters:{},//可以操作异步 和 同步提交stateactions:{}
})
4.State
4.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><style></style>
4.2.批量修改State的值
<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><style></style>
4.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)=>{state.current++;state.age = 40})
}</script><style></style>
4.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 = {current:10,age:30}
}</script><style></style>
4.5.通过actions修改
定义Actions
在actions 中直接使用this就可以指到state里面的值
import { defineStore } from 'pinia';
import { store } from '@/store';export const useModalStore = defineStore({id: 'modal', // 唯一的idstate: () => ({modals: []}),actions: {add(modal) {this.modals.push(modal)},remove(id) {let index = this.modals.findIndex(v => v.id == id)if (index > -1) {this.modals.splice(index, 1);}}},
});// 在组件setup函数外使用
export function useModalStoreWithOut() {return useModalStore(store);
}
使用方法直接在实例调用
<script setup lang='ts'>
iimport { useModalStoreWithOut } from '@/store/modules/modal.store';const modalStore = useModalStoreWithOut();modalStore.remove({ id });
modalStore.add({ modal });</script><style></style>
5.解构store
在Pinia里,是不允许直接解构是会失去响应性的
const Test = useTestStore()const { current, name } = Testconsole.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 } = Testconsole.log(current, name);</script><style>
</style>
解决方案可以使用 storeToRefs
import { storeToRefs } from 'pinia'const Test = useTestStore()const { current, name } = storeToRefs(Test)
其原理跟toRefs 一样的给里面的数据包裹一层toref
源码 通过toRaw使store变回原始数据防止重复代理
循环store 通过 isRef isReactive 判断 如果是响应式对象直接拷贝一份给refs 对象 将其原始对象包裹toRef 使其变为响应式对象
6.Actions,getters
6.1.Actions(支持同步异步)
6.1.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><style></style>
6.1.2.异步 可以结合async await 修饰
import { defineStore } from 'pinia'
import { Names } from './store-naspace'type Result = {name: stringisChu: 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><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><style></style>
6.1.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;}},
6.2.getters
6.2.1.使用箭头函数不能使用this this指向已经改变指向undefined 修改值请用state
主要作用类似于computed 数据修饰并且有缓存
getters:{newPrice:(state)=> `$${state.user.price}`},
6.2.2.普通函数形式可以使用this
getters:{newCurrent ():number {return ++this.current}},
6.2.3.getters 互相调用
getters:{newCurrent ():number | string {return ++this.current + this.newName},newName ():string {return `$-${this.name}`}},
pinia插件
pinia和 vuex 都有一个通病 页面刷新状态会丢失
我们可以写一个pinia 插件缓存他的值
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"}))