Vue3全家桶之——Pinia状态管理

前言

Pinia简称小菠萝🍍,是一个专为Vue 3设计的现代化状态管理库,为Vue 3开发的,它提供了一种简单、可扩展和类型安全的方式来管理应用程序的状态。

Vue 2中的Vuex相比,Pinia提供了更好的TypeScript支持,具有更好的类型定义和类型推断,可在编译时捕获错误,提供更高的代码可靠性和开发体验。它是专为Vue 3设计的,充分利用了Vue 3的新特性,如Composition API,以提供更直接、自然和灵活的状态管理体验。Pinia的核心概念是Store,它类似于Vuex中的模块,用于管理应用程序的状,可以将相关的状态和逻辑组合到单个Store中,使代码更清晰、结构更有组织性。除此之外海提供了许多有用的特性和功能,例如模块化组织、状态持久化、插件扩展等。

总的来说,Pinia是一个功能强大而灵活的状态管理解决方案,适用于各种规模的Vue 3应用程序。它提供了现代化的特性和工具,帮助我们更好地组织、管理和扩展应用程序的状态,同时提供了更好的类型安全和开发体验。

安装

运行安装命令

bash 复制代码
npm install pinia

main.ts中引入

ts 复制代码
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

初始化Store

新建stores文件,用于存放所有的store,然后创建index.ts

同过 defineStore() 定义一个store,它接受一个参数作为仓库名称,也就是Id。它返回一个函数,默认我们使用user开头的风格来接收。第二个参数为一个Setup函数或者Option对象。

ts 复制代码
import { defineStore } from 'pinia'

export const useUsersStore = defineStore('users', {
  // 其他配置...
  
})

Option Store

这种方式熟悉Vuex的很了解,传入一个带有 stateactionsgetters 属性的 Option 对象

ts 复制代码
export const userUsersStore = defineStore('users', {
  state: () => {
    return {
      name: 'inkun',
      current: 100
    }
  },

  getters: {
    getName: (state) => state.name + '🐔你好帅'
  },

  actions:{
    getUserInfo {
      ...
    }
  }
})

Option Store 中:

  • statestore 的数据 data
  • gettersstore 的计算属性 computed
  • actions 则是方法 methods

Setup Store

Vue3 Composition API组合式APIsetup函数相似,传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想要暴露出去的属性和方法的对象。

ts 复制代码
export const userUsersStore = defineStore('users', () => {
  const name = ref('inkun')
  function getInkun() {
    getInkun.value + '🐔你好帅'
  }

  return { name, getInkun }
})

Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

使用Store

定义一个store后,在组件里引入这个store然后就行使用,不需要像ref一样使用.value,可以直接修改访问。

ts 复制代码
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨

const store = useCounterStore()
</script>

State

state定义一个返回初始状态的函数,函数内返回一个对象,里面是需要定义的数据。

对于基础类型而言,[[../TypeScript|TypeScript]]可以自行推断出它们的数据类型,也可以接口,定义state函数返回值。

ts 复制代码
interface State {
  userList: UserInfo[]
  user: UserInfo | null
}

interface UserInfo {
  name: string
  age: number
}
export const userUsersStore = defineStore('users', {
  state: (): State => {
    return {
      userList: [],
      user: null
    }
  }
})

修改State

默认情况下可以直接通过store实例访问state,并且可以直接对其进行读写操作。

Vuex中,如果要对state进行修改必须要定一个mutation,通过mutation进行提交,太过于繁琐。

ts 复制代码
const store = useStore()

store.count++

变更

除了用 store.count++ 直接改变 store,还可以调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性:

ts 复制代码
store.$patch({
  count: store.count + 1,
  name: 'ff',
})

重置

可以通过调用 store$reset() 方法将 state 重置为初始值。

ts 复制代码
const store = useStore()

store.$reset()

监听

类似于 Vuexsubscribe 方法,可以通过 store $subscribe() 方法侦听 state 及其变化。

ts 复制代码
store.$subscribe((mutation, state) => {

  mutation.storeId // 'cart'

  console.log('state change', state)
  console.log('mutation', mutation.type)  // 'direct' | 'patch object' | 'patch function'
  console.log('mutation2', mutation.storeId) // 'users'
  // 只有 mutation.type === 'patch object'的情况下才可用
  // mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
  console.log('mutation3', mutation.payload)
}, {
  detached: true
})

默认情况下,state subscription 会被绑定到添加它们的组件上,当该组件被卸载时,它们将被自动删除。如果想在组件卸载后依旧保留它们,将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离,此时组件卸载后,订阅器还可以使用。

结构State

在使用state时是不允许直接从store中结构数据,这样会导致数据失去响应式和props一样。

解构出来的数据是可以正常访问,当数据修改时是不会发生任何变化。

ts 复制代码
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'

const {current, name} = useCounterStore() // 数据不会发生变化

function change() {
  store.current++
}
</script>

解决方案是通过storeToRefs将数据重新变回响应式。

ts 复制代码
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'

const store= useCounterStore() // 数据不会发生变化
const {name, current} = storeToRefs(store)
function change() {
  store.current = 1
  name.value = 'ff'
}
</script>

Getter

getter相当于计算属性,接收一个函数,函数参数为当前store里的state,也可以通过this去访问。

ts 复制代码
export const userUsersStore = defineStore('users', {
  state: () => {
    return {
      name: 'inkun',
      current: 100
    }
  },
	getUserName(state) {
		return state.name + '🐔你好帅'
	},
	getName(): string {
		return this.name + '🐔你实在太帅'
	}

})

然后就可以通过store实例访问getter

html 复制代码
<template>
  {{ store.getUserName }}
  {{ store.getName }}
</template>

<script setup lang="ts">
import { userUsersStore } from './stores'

const store = userUsersStore()
</script>

访问其他Getter

通过this可以访问其他的getter

ts 复制代码
export const userUsersStore = defineStore('users', {
  state: () => {
    return {
      name: 'inkun',
      current: 100
    }
  },
	getUserName(state) {
		return '大家好,我是' + state.name 
	},
	getName(): string {
		return this.getUserName + '🐔你实在太帅'
	}

})

向Getter传递参数

getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,可以从 getter 返回一个函数,该函数可以接受任意参数:

ts 复制代码
export const userUsersStore = defineStore('users', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})

<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>

访问其他Store里的Getter

将要访问的store引入并实例就可以

ts 复制代码
import { useOtherStore } from './other-store'

export const useStore = defineStore('main', {
  state: () => ({
    // ...
  }),
  getters: {
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.localData + otherStore.data
    },
  },
})

action

action相当于method,和Vuex不同的是它异步同步都可以定义。

ts 复制代码
export const userUsersStore = defineStore('users', {
  state: () => {
    return {
      name: 'inkun',
      current: 100
    }
  },
  actions:{
    async getUserInfo {
      ...
    }
  }
})

getter一样,也可以通过this访问state数据

ts 复制代码
export const userUsersStore = defineStore('users', {
  state: () => {
    return {
      name: 'inkun',
      current: 100
    }
  },
  actions:{
    randomizeCounter() {
      this.count = Math.round(100 * Math.random())
    },
  }
})

在模版上也是和其他一样通过store直接访问。

html 复制代码
<template>
  <button type="button" @click="getUserInfo">获取</button>
</template>

监听

可以通过store.$onAction()来监听 action 和它们的结果。第一个参数为回调函数,可以获取action 的一些信息,第二个参数如果想在组件卸载后依旧保留它们,将 true作为第二个参数传递给action` 订阅器。

它返回一个函数,可以在必要的时候调用函数,此时会删除订阅器取消监听。

ts 复制代码
<script setup lang="ts">
import { userUsersStore } from './stores'

const store = userUsersStore()

const unsubscribe = store.$onAction(({ name, store, args, after, onError }) => {})

// 取消监听
unsubscribe()

</script>

数据持久化

Vuex一样,都存在刷新后数据就会丢失,可以通过pinia-plugin-persistedstate插件来解决。

通过在将数据存储到本地storage中,避免数据刷新丢失。储存位子有两个一个是LocalStorageSessionStorage,具体看个人情况使用。

针对存储的位置,在使用的时候需要考虑项目是否真的要存储在某个位置,合理使用。不能说将用户头像、名称等信息存储在SessionStorage中,网站关闭后数据也还是会丢失。也不能说将IM聊天室消息、所有用户信息等数据存储在LocalStorage中,存储的大小也有限制,这是时候就要使用IndexDBweb SQL等方式。所以需要结合项目功能情况。合理选择存储,而不是一股脑的使用。

安装

bash 复制代码
npm i pinia-plugin-persistedstate

将插件添加到pinia实例上

ts 复制代码
// main.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

使用

在创建store时,设置persist: true

ts 复制代码
export const userUsersStore = defineStore('users', {
  state: () => {
    return {
      name: 'inkun',
      current: 1
    }
  },
  getters: {
    ...
  },
  actions: {
    ...
  },
  persist: true
})

设置完后可以在网页中看到数据存储在localStorage

配置persist

persist可以接收一个对象

ts 复制代码
export const userUsersStore = defineStore('users', {
  state: () => {
    return {
      name: 'inkun',
      current: 1
    }
  },

  persist: {
    key: 'my-custom-key',
    storage: sessionStorage,
    paths: ['current'],
    serializer: {
      deserialize: parse,
      serialize: stringify,
    },
    beforeRestore: (ctx) => {
      console.log(`即将恢复 '${ctx.store.$id}'`)
    },
     afterRestore: (ctx) => {
      console.log(`刚刚恢复完 '${ctx.store.$id}'`)
    },
  }
})
  • key: 用于引用 storage 中的数据,默认使用store中的Id
  • storage:数据存储位置,默认localStorage,可以该为sessionStorage
  • paths:指定state中哪些数据需要持久化
  • serializer:指定持久化时所使用的序列化方法,以及恢复 store 时的反序列化方法。
  • beforeRestore:该 hook 将在从 storage 中恢复数据之前触发,并且它可以访问整个 PiniaPluginContext,这可用于在恢复数据之前强制地执行特定的操作。
  • afterRestore:该 hook 将在从 storage 中恢复数据之后触发,并且它可以访问整个 PiniaPluginContext,这可用于在恢复数据之后强制地执行特定的操作。

全局配置

使用全局配置,就不用单独在每个store里面做配置,在使用pinia use的时候就可以通过createPersistedState函数设置。

ts 复制代码
// main.ts
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'

const pinia = createPinia()

pinia.use(
  createPersistedState({
    storage: sessionStorage,
    paths: ['current'],
  })
)

createPersistedState里的配置会将每个申明persist: truestore添加上配置,但是每个单独store里的配置将会覆盖调全局声明中的对应项

全局配置支持一下属性:

  • storage
  • serializer
  • beforeRestore
  • afterRestore

启用所有 Store 默认持久化

该配置将会使所有 store 持久化存储,且必须配置 persist: false 显式禁用持久化。

ts 复制代码
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'

const pinia = createPinia()

pinia.use(
  createPersistedState({
    auto: true,
  })
)

Store多个持久化配置

在一些特殊情况下,每个store中的数据存储的位置不一样,可以将persist设置为接收多个配置形式。

ts 复制代码
import { defineStore } from 'pinia'

defineStore('store', {
  state: () => ({
    toLocal: '',
    toSession: '',
    toNowhere: '',
  }),
  persist: [
    {
      paths: ['toLocal'],
      storage: localStorage,
    },
    {
      paths: ['toSession'],
      storage: sessionStorage,
    },
  ],
})

强制恢复数据

每个 store 都有 $hydrate 方法来手动触发数据恢复。默认情况下,调用此方法还将触发 beforeRestoreafterRestore 钩子。但是可以通过配置方法来避免这两个钩子触发。

ts 复制代码
import { defineStore } from 'pinia'

const useStore = defineStore('store', {
  state: () => ({
    someData: '你好 Pinia',
  }),
})

调用 $hydrate 方法:

ts 复制代码
const store = useStore()

store.$hydrate({ runHooks: false })

这将从 storage 中获取数据并用它替换当前的 state。并且在上面的示例中,配置了runHooks: false,所以 beforeRestoreafterRestore 钩子函数不会被触发。

强制持久化

除了通过persist方式设置持久化,每个store都有$persist方法来手动触发持久化,这会强制将 store state 保存在已配置的 storage 中。

ts 复制代码
import { defineStore } from 'pinia'

const useStore = defineStore('store', {
  state: () => ({
    someData: '你好 Pinia',
  }),
})

// App.vue
const store = useStore()

store.$persist()

其他文章

相关推荐
passerby606131 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了38 分钟前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅41 分钟前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
吹牛不交税2 小时前
admin.net-v2 框架使用笔记-netcore8.0/10.0版
vue.js·.netcore