Pinia
介绍Pinia
Pinia.js 有如下特点:
- 完整的 ts 的支持;
- 足够轻量,压缩后的体积只有1kb左右;
- 去除 mutations,只有 state,getters,actions;
- actions 支持同步和异步;
- 代码扁平化没有模块嵌套,只有 store 的概念,store 之间可以自由使用,每一个store都是独立的
- 无需手动添加 store,store 一旦创建便会自动添加;
- 支持Vue3 和 Vue2
官方文档Pinia
git 地址 https://github.com/vuejs/pinia
1.起步 安装
yarn add pinia
npm install pinia
2.引入注册
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,
})
初始化仓库Store
1.新建一个文件夹store
2.新建文件[name].ts
3.定义仓库Store
import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main', {
// other options...
})
这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use... 是跨可组合项的约定,以使其符合你的使用习惯。
使用 store
import { useStore } from '@/stores/counter'
export default {
setup() {
const store = useStore()
return {
// 您可以返回整个 store 实例以在模板中使用它
store,
}
},
}
State
大多数时候,state 是 store 的核心部分。 我们通常从定义应用程序的状态开始。 在 Pinia 中,状态被定义为返回初始状态的函数。 Pinia 在服务器端和客户端都可以工作。
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
counter: 0,
name: '浩然',
}
},
})
访问及使用 “state”
默认情况下,您可以通过 store
实例访问状态来直接读取和写入状态:
<template>
<div>pinia:{{countStore.current}}----{{countStore.name}}</div>
<div><button @click="change">pinia++</button></div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useCountStore } from "./store/count";
const countStore = useCountStore()
// 可以直接更改state的值
const change = () => {
countStore.current++
}
</script>
重置状态
将状态 重置 到其初始值
const store = useStore()
store.$reset()
批量改变状态
countStore.$patch({
current: ++countStore.current,
name : '批量更改'
})
批量修改函数形式
countStore.$patch((state) => {
state.current++
state.name = '我被改变了'
})
替换state
通过将其 $state
属性设置为新对象来替换 Store 的整个状态
缺点就是必须修改整个state对象的所有属性
countStore.$state = {
current: 5,
name: '修改整个state'
}
通过actions修改state
import {defineStore} from 'pinia'
export const useCountStore = defineStore('count',{
state: () => {
return {
current: 1,
name: '浩然'
}
},
actions: {
addCurrent(num) {
this.current += num
}
}
})
使用
<template>
<div>pinia:{{countStore.current}}----{{countStore.name}}</div>
<div><button @click="change">pinia++</button></div>
</template>
<script setup lang="ts">
import { useCountStore } from "./store/count";
const countStore = useCountStore()
const change = () => {
countStore.addCurrent(2)
}
</script>
订阅状态
通过 store 的 $subscribe()
方法查看状态及其变化,类似于 Vuex 的 subscribe 方法
与常规的 watch()
相比,使用 $subscribe()
的优点是 订阅 只会在 patches 之后触发一次
countStore.$subscribe((args, state) => {
console.log(args);
console.log(state);
})
解构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>
解决方案可以使用 storeToRefs
import { storeToRefs } from 'pinia'
const Test = useTestStore()
const { current, name } = storeToRefs(Test)
其原理跟toRefs 一样的给里面的数据包裹一层toref
源码 通过toRaw使store变回原始数据防止重复代理
循环store 通过 isRef isReactive 判断 如果是响应式对象直接拷贝一份给refs 对象 将其原始对象包裹toRef 使其变为响应式对象
Actions异步操作
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>
<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>
订阅Actions的调用
$onAction
只要有actions被调用就会走这个函数
countStore.$onAction((args => {
console.log(args);
}))
getters
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count', {
state: () => {
return {
current: 1,
name: '浩然'
}
},
getters: {
getName(): string {
return 'getters-' + this.name
}
},
})
使用:
<template>
<div>{{countStore.getName}}</div>
</template>
<script setup lang="ts">
import { useCountStore } from "./store/count";
const countStore = useCountStore()
</script>
getters相互调用
getters:{
newCurrent ():number | string {
return ++this.current + this.newName
},
newName ():string {
return `$-${this.name}`
}
},
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"
}))