1. 安装
npm i pinia -S
2. 项目中引入和使用
main.ts
// vue3 版本api
import { createPinia } from 'pinia'
// 返回一个vue插件
const store = createPinia()
// 使用pinia插件
app.use(store)
3. 创建store
store
是一个用reactive
包裹的对象- 可以根据需要定义任意数量的 store ,我们以一个userStore为例
- pinia怎么知道有多少个store呢?(答案是,只要定义的store执行了defineStore返回的函数,pinia就认为这个store激活了)
import { defineStore } from 'pinia'
// defineStore('user', ...) 和下面的形式相同
export const useUserStore = defineStore({
id: 'user',
state: () => ({
userid: localStorage.getItem('userid') || '',
}),
actions: {
setUserId(payload: string) {
this.userid = payload
}
}
})
4. state
4.1 获取state
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
// 方式1,计算属性方式
const userid = computed(() => useUserStore().userid)
// 方式2, 通过user.userid的方式使用
const user = useUserStore()
// 方式3, 使用toRef获取userid
const userid = toRef(useUserStore(), 'userid')
// 方式4, 借助pinia提供的api: storeToRefs 实现
const { userid } = storeToRefs(user)
</script>
4.2 修改state
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
const user = useUserStore()
// 方式1: 直接修改,vuex不允许这种方式(需要提交mutation),但pinia是允许的
user.userid = 'xxx'
// 方式2:
user.$patch({userid: 'xxx'})
// 方式3:
user.$patch((state) => { state.userid = 'xxx' })
// 方式4:
user.$state = { userid:'xxx' }
// 方式5: 使用actions
user.setUserId('xxx')
</script>
5. actions
- actions包含同步actions和异步actions
const login = (): Promise<string> => {
return new Promise(resolve => {
setTimeout(() => {
resolve('userid')
}, 2000);
})
}
// defineStore('user', ...) 和下面的形式相同
export const useUserStore = defineStore({
id: 'user',
state: () => ({
userid: localStorage.getItem('userid') || '',
}),
actions: {
// 同步actions
setUserId(payload: string) {
console.log(payload)
this.userid = payload
},
// 异步actions
async getUser() {
// actions可以互相调用
this.setUserId(await login())
}
}
})
- actions可以互相调用,我们把actions调用关系互换一下
actions: {
// 在同步actions中调用异步actions
setUserId(payload: string) {
console.log(payload)
this.getUser()
},
async getUser() {
// this.setUserId(await login())
this.userid = await login()
}
}
6. getters
-
相当于组件中的computed也具有缓存
-
接收"状态"作为第一个参数
-
state: () => ({ userid: localStorage.getItem('userid') || '', counter: 0 }), getters: { doubleCount: (state) => state.counter * 2, },
6.1 常规函数使用this
的注意事项
-
定义常规函数时可以通过
this
访问到 整个 store 的实例, 但是需要定义返回类型(在 TypeScript 中)。 这是由于 TypeScript 中的一个已知限制,并且不会影响使用箭头函数定义的 getter,也不会影响不使用 this 的 getter: -
export const useStore = defineStore('main', { state: () => ({ counter: 0, }), getters: { // 自动将返回类型推断为数字 doubleCount(state) { return state.counter * 2 }, // 返回类型必须明确设置 doublePlusOne(): number { // 调用其他getter: return this.doubleCount + 1 // 等同于: return this.counter * 2 + 1 }, }, })
6.2 接收参数传递
-
Getters 只是幕后的 computed 属性,因此无法向它们传递任何参数。 但是,您可以从 getter 返回一个函数以接受任何参数:
-
这种操作getter不再缓存,只相当于在调用函数(从store 的解构中可以看出)
-
export const useStore = defineStore('main', { getters: { getUserById: (state) => { return (userId) => state.users.find((user) => user.id === userId) }, }, })
-
组件
-
// store getters: { doubleCount: (state) => state.counter * 2, doublePlusOne(): number { // 等同于调用其他getter: return this.doubleCount + 1 return this.counter * 2 + 1 }, payloadCount() { return (payload: number) => this.doublePlusOne + payload } }, // 组件 const { userid, payloadCount, } = storeToRefs(user)
-
模板
-
<main> home: {{ user.userid }} {{ doubleCount }} {{ payloadCount(3) }} </main>
7. store apis
-
$reset
重置状态 -
store.$reset()
-
$patch
派发actions
-
// 方式1: user.$patch({userid: 'xxx'}) // 方式2: cartStore.$patch((state) => { state.items.push({ name: 'shoes', quantity: 1 }) state.hasChanged = true })
-
$state
访问 store 状态 -
store.$state = { counter: 666, name: 'Paimon' }
-
$subscribe
, 订阅变更- 优点:即使触发了某个action,但newval 和 oldval相等也不会触发这个订阅
-
cartStore.$subscribe((mutation, state) => { // import { MutationType } from 'pinia' mutation.type // 'direct' | 'patch object' | 'patch function' // 与 cartStore.$id 相同 mutation.storeId // 'cart' // 仅适用于 mutation.type === 'patch object' mutation.payload // 补丁对象传递给 to cartStore.$patch() // 每当它发生变化时,将整个状态持久化到本地存储 localStorage.setItem('cart', JSON.stringify(state)) })
// 可以利用该api针对某些 mutation.events.key 结合 localStorage 做判断实现数据持久化 user.$subscribe((mutation, state) => { /** * 下面是 mutation的属性 * key: "counter" newValue: 1 oldValue: 0 target: {userid: 'userid', counter: 1} type: "set" oldTarget: undefined */ console.log('$%#', mutation.events as any, state) })
$onAction
, 监听actions触发可以利用after做一些事情
context.store.$onAction((arg) => { // arg 属性 /** * after: ƒ after(callback) args: [] actions 参数 name: "setUserId" onError: ƒ onError(callback) store: action所在实例 * */ if(arg.name === '"setUserId"') { console.log('onaction', arg) arg.after(() =>{ // do something }) } })
8. plugins
- pinia 插件是一个函数,可以选择返回要添加到store的属性,
- 该函数可以接收一个可选参数: context (ts类型:PiniaPluginContext)
- context.pinia // 使用
createPinia()
创建的 pinia - context.app // 使用
createApp()
创建的当前应用程序(仅限 Vue 3) - context.store // 插件正在扩充的 store
- context.options // 定义存储的选项对象传递给
defineStore()
- context.pinia // 使用
- 只有store调用了导出的useXXX函数,插件函数才会运行并对该store进行处理
import type { PiniaPluginContext } from 'pinia'
// 持久化插件
export function persistencePlugin(context: PiniaPluginContext) {
/**
* key: "counter"
newValue: 1
oldValue: 0
target: {userid: 'userid', counter: 1}
type: "set"
oldTarget: undefined
*/
context.store.$subscribe((mutation: any) => {
if (mutation.storeId === 'user') {
if (mutation.events.key === 'userid') {
localStorage.setItem('userid', mutation.events.newValue)
}
}
})
/**
* after: ƒ after(callback)
args: [] actions 参数
name: "setUserId"
onError: ƒ onError(callback)
store: action所在实例
*
*/
context.store.$onAction((arg) => {
if (arg.name === '"setUserId"') {
console.log('onaction', arg)
}
})
}
9. mock数据
1. npm i mockjs vite-plugin-mock
2.在vite.config.ts中加入配置
import { viteMockServe } from "vite-plugin-mock";
plugins: [
viteMockServe({
supportTs: true,
logger: false,
mockPath: "./mock/",
}),
],
3. 在根文件同等路径下去建立一个文件夹,取名mock.ts
4. 然后写入想要请求的接口的数据
export default [
{
url: "/api/banner/list",
method: "get",
response: () => {
return {
code: "200",
message: "获取成功",
data: [
{
img: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimages.uiiiuiii.com%2Fwp-content%2Fuploads%2F2019%2F07%2Fi-bn20190630-1-15.jpg&refer=http%3A%2F%2Fimages.uiiiuiii.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1671816683&t=35a1fc5f8bd1483aa69fbd16dc7e7524",
alt: "112",
link: "112",
bannerid: "banner_6a1f7001-e580-4d8b-b5dd-7447f94c1990",
flag: true,
},
{
img: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01f6ad5dde2b7ca80120686b55f619.jpg%3Fx-oss-process%3Dimage%2Fauto-orient%2C1%2Fresize%2Cm_lfit%2Cw_1280%2Climit_1%2Fsharpen%2C100&refer=http%3A%2F%2Fimg.zcool.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1671816683&t=a4402cf070e22b867a74cc0d23a8c4e7",
alt: "1123",
link: "1123",
bannerid: "banner_35f7aa34-6745-4f88-934b-7b506330a695",
flag: true,
},
{
img: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F013aa3594fac92a8012193a3cabdb4.jpg&refer=http%3A%2F%2Fimg.zcool.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1671816683&t=0009e5acc265a854d79350e84b9cbb2a",
alt: "11234",
link: "11234",
bannerid: "banner_52cd31c3-7811-461c-9f1a-239845ab0578",
flag: true,
},
{
img: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0183245697b4f66ac725af23df2dcc.jpg&refer=http%3A%2F%2Fimg.zcool.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1671816683&t=c60c7fb9f4d4935208407b8c8e874c74",
alt: "112345",
link: "112345",
bannerid: "banner_1ba6cee8-d2de-47c2-955b-cd5c773f6d70",
flag: true,
},
],
};
},
},
];