Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的
export const state = reactive({})
来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:
- Devtools 支持
- 追踪 actions、mutations 的时间线
- 在组件中展示它们所用到的 Store
- 让调试更容易的 Time travel
- 热更新
- 不必重载页面即可修改 Store
- 开发时可保持当前的 State
- 插件:可通过插件扩展 Pinia 功能
- 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
- 支持服务端渲染
与 Vuex 相比,Pinia 不仅提供了一个更简单的 API,也提供了符合组合式 API 风格的 API,最重要的是,搭配 TypeScript 一起使用时有非常可靠的类型推断支持。
Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的。
Pinia API 与 Vuex(<=4) 也有很多不同,即:
- mutation 已被弃用。它们经常被认为是极其冗余的。它们初衷是带来 devtools 的集成方案,但这已不再是一个问题了。
- 无需要创建自定义的复杂包装器来支持 TypeScript,一切都可标注类型,API 的设计方式是尽可能地利用 TS 类型推理。
- 无过多的魔法字符串注入,只需要导入函数并调用它们,然后享受自动补全的乐趣就好。
- 无需要动态添加 Store,它们默认都是动态的,甚至你可能都不会注意到这点。注意,你仍然可以在任何时候手动使用一个 Store 来注册它,但因为它是自动的,所以你不需要担心它。
- 不再有嵌套结构的模块。你仍然可以通过导入和使用另一个 Store 来隐含地嵌套 stores 空间,虽然是 Pinia 从设计上提供的是一个扁平的结构,但仍然能够在 Store 之间进行交叉组合。你甚至可以让 Stores 有循环依赖关系。
- 不再有可命名的模块。考虑到 Store 的扁平架构,Store 的命名取决于它们的定义方式,你甚至可以说所有 Store 都应该命名。
关于如何将现有 Vuex(<=4) 的项目转化为使用 Pinia 的更多详细说明,请参阅 Vuex 迁移指南
yarn add pinia
# 或者使用 npm
npm install pinia
1.直接在 main.js 创建
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 引入pinia
import App from './App.vue'
const pinia = createPinia() // 创建实例
const app = createApp(App)
app.use(pinia) // 安装插件
app.mount('#app')
2.以上创建方式方便很多但是后期如果我们需要添加扩展内容的话 main.js 会变得很大,所以比较推荐以下这种创建方式,在以下的案例中,都会以以下的方式来说明
src/store/index.js
import { createPinia } from 'pinia' //创建pinia
const pinia = createPinia() //实例化
export default pinia
main.js
import { createApp } from 'vue'
import App from './App.vue'
import pinia from './store' //引入创建好的pinia实例
const app = createApp(App)
app.use(pinia) //全局挂载
app.mount('#app')
Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念,state、getter 和 action,我们可以假设这些概念相当于组件中的
data
、computed
和methods
。
defineStore()
的第二个参数可接受两类值:Setup 函数或 Option 对象。
Option Store:与 Vue 的选项式 API 类似,我们也可以传入一个带有 state、
actions
与 getters
属性的 Option 对象
import { defineStore } from 'pinia'
// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,
// 同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useStore = defineStore('main', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
// 你可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),
// 而 actions 则是方法 (methods)。
Setup Store:也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment () {
count.value++
}
return { count, double, increment }
})
// 在 Setup Store 中:
// ref() 就是 state 属性
// computed() 就是 getters
// function() 就是 actions
我是app
{{ counter.count }}
state
import { useCounterStore } from './store/counter' // 导入创建好的counter.js
const counter = useCounterStore() // 实例化
console.log(counter.count) // 0
import { useCounterStore } from './store/counter' // 导入创建好的counter.js
const counter = useCounterStore() // 实例化
counter.$reset() // store 的 $reset() 方法将 state 重置为初始值。
state
默认情况下,state subscription 会被绑定到添加它们的组件上(如果 store 在组件的
setup()
里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将{ detached: true }
作为第二个参数,以将 state subscription 从当前组件中分离:
export const useCounterStore = defineStore('Counter',{
state: () => {
return {
name: '快乐超人',
}
},
getters: {
formatName: (state) => {
return state.name + '00';
},
},
})
import { useCounterStore } from './store/counter'
const counter = useCounterStore()
counter.formatName //快乐超人00
如果需要向 getters 定义的函数传入参数:
export const useCounterStore = defineStore('Counter', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
import { useCounterStore } from './store/counter'
const counter = useCounterStore()
counter.getUserById(2)