什么是 Pinia
Pinia(发音为/piːnjʌ/,如英语中的“peenya”)是最接近有效包名 piña(西班牙语中的 pineapple,即“菠萝”)的词。菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。
Pinia 最初是为了探索 Vuex 的下一次迭代可能会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它取而代之的是新的建议。与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的仪式,提供了Composition-API风格的 API,最重要的是,在与TypeScript一起使用时具有可靠的类型推断支持。
Pinia 适用于 Vue 2 和 Vue 3 ,除非安装方式和 SSR 之外,两者的 API 都是相同的。适用于 Composition API (组合式 API)和 Options API(选项式 API)。
为什么要使用 Pinia?
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉 Composition API 的话,你可能会认为可以通过一行简单的export const state = reactive({})来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:
- 开发工具支持
- 追踪 actions、mutations 的时间线
- 在组件中展示它们所用到的 Store
- 让调试更容易的 Time travel
- 热更新
- 不必重载页面即可修改 Store
- 开发时可保持当前的 State
- 插件:可通过插件扩展 Pinia 功能
- 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能
- 支持服务端渲染
基本示例
Option 式
这就是使用 Pinia 在 API 方面的样子。您首先创建一个 store:
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state : () => {
return { count: 0 }
},
// 也可以这样定义
// state: () => ({ count: 0 })
actions : {
increment() {
this.count++
},
},
})
然后在组件中使用它:
import { useCounterStore } from '@/stores/counter'
export default {
setup() {
const counter = useCounterStore()
counter.count++
// 带有自动补全
counter.$patch({ count: counter.count + 1 })
// 或者使用 action 代替
counter.increment()
},
}
Setup 式
你甚至可以使用一个函数(类似于一个组件setup())来为更高级的用例定义一个 Store:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
map 助手
如果您仍然不熟悉 Composition API 的setup(),请不要担心,Pinia 还支持一组类似 Vuex 的 map 助手。您以相同的方式定义存储,但随后使用mapStores()、mapState()或mapActions():
const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
const useUserStore = defineStore('user', {
// ...
})
export default {
computed: {
// 其他计算属性
// ...
// 允许访问 this.counterStore 和 this.userStore
...mapStores(useCounterStore, useUserStore),
// 允许读取 this.count 和 this.double
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
// 允许读取 this.increment()
...mapActions(useCounterStore, ['increment']),
},
}
一个实例
这是一个更完整的 API 示例,您将在 Pinia 中使用此样式,即使在 JavaScript 中也是如此。
import { defineStore } from 'pinia'
export const useTodos = defineStore('todos', {
state: () => ({
/** @type {{ text: string, id: number, isFinished: boolean }[]} */
todos: [],
/** @type {'all' | 'finished' | 'unfinished'} */
filter: 'all',
// type will be automatically inferred to number
nextId: 0,
}),
getters: {
finishedTodos (state) {
// autocompletion!
return state.todos.filter((todo) => todo.isFinished)
},
unfinishedTodos (state) {
return state.todos.filter((todo) => !todo.isFinished)
},
/**
* @returns {{ text: string, id: number, isFinished: boolean }[]}
*/
filteredTodos (state) {
if (this.filter === 'finished') {
// call other getters with autocompletion
return this.finishedTodos
} else if (this.filter === 'unfinished') {
return this.unfinishedTodos
}
return this.todos
},
},
actions: {
// any amount of arguments, return a promise or not
addTodo (text) {
// you can directly mutate the state
this.todos.push({ text, id: this.nextId++, isFinished: false })
},
},
})
