介绍#
Pinia最初是在 2019 年 11 月左右重新设计使用Composition API的 Vue 商店外观的实验。从那时起,最初的原则仍然相同,但 Pinia 适用于 Vue 2 和 Vue 3 ,并且不需要你使用组合 API。除了安装和SSR之外,两者的 API 都是相同的,并且这些文档针对 Vue 3 ,并在必要时提供有关 Vue 2 的注释,以便 Vue 2 和 Vue 3 用户可以阅读!
为什么要使用 Pinia?#
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。如果您熟悉 Composition API,您可能会认为您已经可以使用简单的export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,则会将您的应用程序暴露给安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:
-
开发工具支持
- 跟踪动作、突变的时间表
- 商店出现在使用它们的组件中
- 时间旅行和更容易的调试
-
热模块更换
- 在不重新加载页面的情况下修改您的商店
- 在开发时保持任何现有状态
-
插件:使用插件扩展 Pinia 功能
-
为 JS 用户提供适当的 TypeScript 支持或自动完成功能
-
服务器端渲染支持
基本示例#
这就是使用 pinia 在 API 方面的样子(请务必查看入门以获取完整说明)。您首先创建一个商店:
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
// could also be defined as
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++
},
},
})
然后在组件中使用它:
import { useCounterStore } from '@/stores/counter'
export default {
setup() {
const counter = useCounterStore()
counter.count++
// with autocompletion ✨
counter.$patch({ count: counter.count + 1 })
// or using an action instead
counter.increment()
},
}
你甚至可以使用一个函数(类似于一个组件setup())来为更高级的用例定义一个 Store:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
如果您仍然不熟悉setup()Composition API,请不要担心,Pinia 还支持一组类似的地图助手,例如 Vuex。您以相同的方式定义存储,但随后使用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: {
// other computed properties
// ...
// gives access to this.counterStore and this.userStore
...mapStores(useCounterStore, useUserStore),
// gives read access to this.count and this.double
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
// gives access to this.increment()
...mapActions(useCounterStore, ['increment']),
},
}
您将在核心概念中找到有关每个地图助手的更多信息。
为什么选择Pinia#
Pinia(发音为/piːnjʌ/,如英语中的“peenya”)是最接近piña(西班牙语中的菠萝)的词,它是一个有效的包名称。菠萝实际上是一组单独的花朵,它们结合在一起形成多个水果。与商店类似,每一家都是独立诞生的,但最终都是相互联系的。它也是一种美味的热带水果,原产于南美洲。
一个更现实的例子#
这是一个更完整的 API 示例,您将在 Pinia中使用类型,即使在 JavaScript 中也是如此。对于某些人来说,这可能足以在不进一步阅读的情况下开始使用,但我们仍然建议您查看文档的其余部分,甚至跳过此示例并在阅读完所有核心概念后返回。
import { defineStore } from 'pinia'
export const todos = 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 })
},
},
})
与 Vuex 的比较#
Pinia 最初是为了探索 Vuex 的下一次迭代可能会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它取而代之的是新的建议。
与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的仪式,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。
RFC#
最初 Pinia 没有通过任何 RFC。我根据我开发应用程序、阅读其他人的代码、为使用 Pinia 的客户工作以及在 Discord 上回答问题的经验测试了想法。这使我能够提供一种适用于各种情况和应用程序大小的解决方案。我过去经常发布并在保持其核心 API 不变的同时使库不断发展。
现在 Pinia 已经成为默认的状态管理解决方案,它和 Vue 生态系统中的其他核心库一样遵循 RFC 流程,其 API 也进入了稳定状态。
与 Vuex 3.x/4.x 的比较#
Vuex 3.x 是 Vuex 的 Vue 2 而 Vuex 4.x 是 Vue 3
Pinia API 与 Vuex ≤4 有很大不同,即:
- 突变不再存在。他们经常被认为是非常冗长的。他们最初带来了 devtools 集成,但这不再是问题。
- 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
- 不再需要注入魔法字符串、导入函数、调用它们,享受自动完成功能!
- 无需动态添加商店,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用商店进行注册,但因为它是自动的,您无需担心。
- 不再有模块的嵌套结构。您仍然可以通过在另一个商店中导入和使用商店来隐式嵌套商店,但 Pinia 通过设计提供平面结构,同时仍然支持商店之间的交叉组合方式。你甚至可以有 store 的循环依赖。
- 没有命名空间的模块。鉴于商店的扁平架构,“命名空间”商店是其定义方式所固有的,您可以说所有商店都是命名空间的。
有关如何将现有 Vuex ≤4 项目转换为使用 Pinia 的更详细说明,请参阅从 Vuex 迁移指南。
安装#
pinia使用您最喜欢的包管理器安装:
yarn add pinia
# or with npm
npm install pinia
提示
如果您的应用使用 Vue 2,您还需要安装组合 api: @vue/composition-api。如果您使用 Nuxt,则应遵循这些说明。
如果你使用的是 Vue CLI,你可以试试这个非官方的插件。
创建一个 pinia(根存储)并将其传递给应用程序:
import { createPinia } from 'pinia'
app.use(createPinia())
如果您使用的是 Vue 2,您还需要安装一个插件并pinia在应用程序的根目录注入创建的插件:
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
// other options...
// ...
// note the same `pinia` instance can be used across multiple Vue apps on
// the same page
pinia,
})
这也将添加 devtools 支持。在 Vue 3 中,仍然不支持时间旅行和编辑等一些功能,因为 vue-devtools 尚未公开必要的 API,但 devtools 具有更多功能,并且整体开发人员体验要优越得多。在 Vue 2 中,Pinia 使用 Vuex 的现有接口(因此不能与它一起使用)。