目录

---------

Pinia 基本介绍

Pinia 是 Vue 的存储库,允许我们跨组件/页面共享状态

主要:

Store

store:仓库,我们数据都放在 store 里面,这些数据其他所有的组件都可以访问和修改。

定义一个 Store

Typscript
import { defineStore } from 'pinia'
// useStore 可以是 useUser、userCart
// 第一个参数是应用程序 store 的唯一 id
export const useStore = defineStore('main', {
    // other options...
})

这个 name,也称为 id 是必要的。使用 useXxx 是跨可组合项的约定,符合 hooks 习惯

使用 store

useStoresetup() 中调用,之前不会创建 store:

Vue
<script lang="ts" setup>
import { useStore } form '@/stores/counter'
const store = useStore() // store 实例可以在整个模板中使用
</script>

**注意:**对 store 解构,会失去响应式

Typscript
// 错误的,会破坏响应式
const { name } = useStore()

为了从 Store 中提取属性同时保持响应式,需要使用 storeToRefs() 。它将为任何响应式属性创建 refs。

注意:解构函数时不可以用 storeToRefs

Typscript
const store = useStore()
const { name, doubleCount } = storeToRefs(store)

State

state 基本使用

Typscript
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
    // 推荐箭头函数形式
    state: () => {
        return {
            // 会自动推断类型,这里定义的是默认值
            count: 0,
            name: 'xj',
            isAdmain: true
        }
    }
})

访问 state

默认情况下,可以通过 store 实例访问状态来直接读取和写入

Typscript
const store = useStore()
store.counter++

$reset()

可以通过 store 上的 $reset() 方法将状态重置到初始值

Typscript
const store = useStore()
store.$reset

$patch

除了直接用 store.counter++ 修改 store,还可以调用 $patch 方法,这个方法允许我们对 store 的多个对象同时修改

Typscript
store.$patch({
    counter: store.counter + 1,
    name: 'xjj'
})

$patch 也可以接受一个函数来批量修改集合内部分对象的情况,这样使用数组的 push 等方法修改数据会容易些

Typscript
store.$patch((state) => {
    state.couter ++
    state.name = 'xjj'
})

$state

$state 可以将新对象替换 store 的整个状态

Typscript
store.$state = { counter: 666, name: 'xjj' }

$subscribe()

Pinia 的 $subscribe() 和 Vue 的 watch 类似,优点是 subscriptions 只会在 patches 之后触发一次

Typscript
userStore.$subscribe((mutation, state) => {
    mutation.type // 'direct' | 'patch object' |'patch function'
    // 与 userStore.$id 相同
    mutation.storeId // 'user'
	// 仅适用于 mutation.type === 'patch object'
    mutation.payload // 补丁对象传递给 userStore.$patch()
    // 每当它发生变化时,将整个状态持久化到本地存储
    localStorage.setItem('user', JSON.stringify(state))
})

state subscriptions 默认情况下是绑定到添加它的组件上,意味着当组件卸载后,将会被自动删除。如果希望组件被卸载时仍保留它们,可以将 $subscribe 第二个参数设置为 { detached: true }

Typscript
userStore.$subscribe(cllbackFn, { detached: true })

Getters

getters 相当于 Vue 中的 computed,等同于 store 的计算值

当我们使用 this 来访问 store 的实例时,需要指定返回类型

Typscript
defineStore('main', {
    state: () => {
        return {
            counter: 0
        }
    },
    getters: {
        // 返回类型必须明确
        doubleCounter(): number {
            return this.counter * 2
        },
        // 将自动推断为数字
        counterAddOne(state): number {
            return this.counter + 1
        }
    }
})

访问其他 getter

我们可以通过 this 访问其他的 getter。

使用 this 访问其他 getter,不需要特意指定返回值类型

TSX
export const useStore = defineStore({
    state: () => {
        return { counter: 0 }
    },
    getters: {
        doubleCount: (state) => state.counter * 2,
        ddCount: () => this,doubleCoun * 2
    }
})

将参数传递给 getter

getters 本身无法传递参数,但是我们可以返回一个函数,其函数可以接受任意函数

Typscript
export const useStore = defineStore({
    getters: {
        getUserById: () => {
            return (userId) => {
                return state.users.find(user => user.id === userId)
            }
        }
    }
})

在组件中使用:

Vue
<script lang="ts" setup>
const store = useStore()
</script>
<template>
	<p>User 2:{{ getUserById(2) }}</p>
</template>

使用函数作为返回值,getter 不再缓存,他们只是调用的函数。但是我们可以在 getter 本身内部缓存一些结果,虽然不常见,但是性能更高:

Typscript
export const useStore = defineStore('main', {
  	getters: {
    	getActiveUserById(state) {
      	const activeUsers = state.users.filter((user) => user.active)
      	return (userId) => activeUsers.find((user) => user.id === userId)
    	},
  	},
})

Actions

Action 相当于组件中的 methods。适合定义业务逻辑

Typscript
export const useStore = defineStore('main', {
    state: () => { counter: 0 },
    actions: {
        increment() {
            this.counter ++
        },
        randomizeCounter() {
            this.counter = Math.round(100 * Math.random())
        }
    }
})

与 getters 一样,操作可以通过 this 访问 store 实例,并提供完整的类型支持。但是有一点不同的是,actions 可以是异步的,例如下面示例

Typscript
export const useStore = defineStore('main', {
    state: () => { weatherData: null },
    actions: {
        async initWeatherData() {
            this.weatherData = await fetch('http://plumbiu.club:8001')
        }
    }
})

Plugins

基本使用

Pinia 插件是一个函数,可以选择返回要添加到 store 的属性,它需要一个可选参数,一个 context:

Typscript
export function myPiniaPlugin(context) {
    context.pinia 	// 使用 `createPinia()` 创建的 pinia
    context.app		// 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
    context.store	// 插件正在扩充的 store
    context.options // 定义存储的选项对象,传递给 `defineStore()`
}

之后使用 pinia.use 将函数传递给 pinia

Typscript
pinia.use(myPiniaPlugin)

扩充 store

可以用以下方式简单地在插件中返回它们的对象,来为每个 store 添加属性:

Typscript
pinia.use(() => ({ hello: 'world' }))

也可以直接在 store 上设置属性。

Typscript
pinia.use(({ store }) => {
    store.hello = 'world'
})

**注意:**每个 store 都是用了 reactive 包装,这也是没有使用 .value 语法的情况下访问所有的计算属性以及他们为什么是响应式的原因

自定义持久化插件

Typscript
// src/store/plugins/persistedstate.ts
import type { PiniaPluginContext } from 'pinia'
/**
 * 插件方法,有几个模块执行几次
 * @param context
*/
export function persistedstate(context: PiniaPluginContext) {
    console.log('初始化的时候执行', context.store.$id)
    const currentState = JSON.parse(localStorage.gertItem(context.$id))
    // 把数据放到 Pinia 对应的模块里面
    context.store.$patch(currentState)
    /**
     *  每次 state 发生变化,都要把它存放在 localStorage 里面
     * 两个参数
     *	1.当前修改 store 的上下文
     *	2.当前修改的状态
    */
    context.store.$subscribe((_store, state) => {
        localStorage.setItem(_store.storeId, JSON.stringify(state))
    }, {
        // 组件卸载依赖还在
        detached: true
    })
}
Typscript
import { createPinia } from 'pinia'
import { persistedstate } from './plugins/persistedstate'
const pinia = createPinia()
pinia.use(persistedstate)
export default pinia