Pinia介绍 Introduction

Pinia(发音为 `/piːnjʌ/`,类似于英语中的“peenya”)是最接近有效包名piña的词。 菠萝实际上是一组单独的花朵,它们结合在一起形成多个水果。 与商店类似,每一家都是独立诞生的,但最终都是相互联系的。 它也是一种美味的热带水果,原产于南美洲。

Pinia 开始 是一项实验,目的是在 2019 年 11 月左右使用 Composition API 重新设计 Vue 商店的外观。从那时起,最初的原则保持不变,但 Pinia 适用于两种 Vue 2 和 Vue 3 并且不要求您使用组合 API。 除了 installationSSR 之外,两者的 API 是相同的,并且这些文档针对 Vue 3 并在必要时提供有关 Vue 2 的注释,以便 Vue 2 和 Vue 3 用户可以阅读!

为什么要使用 Pinia?

Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。 如果您熟悉 Composition API,您可能会认为您已经可以通过简单的 export const state = reactive({}) 共享全局状态。 这对于单页应用程序来说是正确的,但 将您的应用程序暴露给 安全漏洞 如果是 服务器端渲染。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:

  • 开发工具支持
    • 跟踪动作、突变的时间线
    • 商店出现在使用它们的组件中
    • 时间旅行和更容易的调试
  • 热模块更换
    • 在不重新加载页面的情况下修改您的商店
    • 在开发时保持任何现有状态
  • 插件:使用插件扩展 Pinia 功能
  • 为 JS 用户提供适当的 TypeScript 支持或 autocompletion
  • 服务器端渲染支持

基本示例

这就是使用 Pinia 在 API 方面的样子(请务必查看 Getting Started 以获取完整说明)。 您首先创建一个商店:

js
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // 也可以定义为 state:() => ({ count: 0 })
  actions: {
    increment() {
      this.count++
    },
  },
})

然后你在一个组件中 使用 它:

vue
<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

counter.count++
// 带自动补全 ✨
counter.$patch({ count: counter.count + 1 })
// 或使用动作代替
counter.increment()
</script>

<template>
  <!-- 直接从商店访问状态 -->
  <div>Current Count: {{ counter.count }}</div>
</template>

你甚至可以使用一个函数(类似于一个组件setup())来为更高级的用例定义一个Store:

js
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }

  return { count, increment }
})

如果您仍然不了解 setup() 和 Composition API,别担心,Pinia 还支持一组类似的 map helpers like Vuex。 您以相同的方式定义存储,然后使用 mapStores()mapState()mapActions()

js
const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

const useUserStore = defineStore('user', {
  // ...
})

export default defineComponent( {
  computed: {
    // 其他计算属性
    // ...
    // 提供对 this.counterStore 和 this.userStore 的访问权限
    ...mapStores(useCounterStore, useUserStore),
    // 授予对 this.count 和 this.double 的读取权限
    ...mapState(useCounterStore, ['count', 'double']),
  },
  methods: {
    // 允许访问 this.increment()
    ...mapActions(useCounterStore, ['increment']),
  },
})

您将在核心概念中找到有关每个 map helper 的更多信息。

Pinia 名称来意

Pinia(发音为 /piːnjʌ/,类似于英语中的“peenya”)是最接近有效包名 piña(西班牙语中的_pineapple_)的词。 菠萝实际上是一组单独的花朵,它们结合在一起形成多个水果。 与商店类似,每一家都是独立诞生的,但最终都是相互联系的。 它也是一种美味的热带水果,原产于南美洲。

一个更现实的例子

这是一个更完整的 API 示例,您将与 Pinia 一起使用即使在 JavaScript 中也具有类型。 对于某些人来说,这可能足以在不进一步阅读的情况下开始使用,但我们仍然建议您查看文档的其余部分,甚至跳过此示例,并在阅读完所有_核心概念_后返回。

js
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 会自动推断为 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') {
        // 使用自动完成调用其他 getter ✨
        return this.finishedTodos
      } else if (this.filter === 'unfinished') {
        return this.unfinishedTodos
      }
      return this.todos
    },
  },
  actions: {
    // 任何数量的参数,是否返回一个承诺
    addTodo(text) {
      // 你可以直接改变状态
      this.todos.push({ text, id: this.nextId++, isFinished: false })
    },
  },
})

与 Vuex 的比较

Pinia 最初是为了探索 Vuex 的下一次迭代可能会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它 取而代之的是新的建议。

与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的仪式,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。

RFCs

最初 Pinia 没有经过任何 RFC 流程。 我根据我开发应用程序、阅读其他人的代码、为使用 Pinia 的客户工作以及在 Discord 上回答问题的经验来测试想法。 这使我能够提供一个适用于各种情况和应用程序大小的解决方案。 我曾经经常发布并使库不断发展,同时保持其核心 API 不变。

现在 Pinia 已经成为默认的状态管理解决方案,它和 Vue 生态系统中的其他核心库一样遵循 RFC 流程,其 API 也进入了稳定状态。

Comparison with Vuex 3.x/4.x

Vuex 3.x is Vuex for Vue 2 while Vuex 4.x is for Vue 3

Pinia API 与 Vuex ≤4 有很大不同,即:

  • mutations 不再存在。 他们通常被认为是_极其_冗长。 他们最初带来了 devtools 集成,但这不再是问题。
  • 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
  • 不再需要注入、导入函数、调用函数、享受自动完成功能!
  • 无需动态添加商店,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用商店进行注册,但因为它是自动的,您无需担心。
  • 不再有_modules_的嵌套结构。您仍然可以通过在另一个商店中导入和_使用_ 来隐式嵌套商店,但 Pinia 通过设计提供平面结构,同时仍然支持商店之间的交叉组合方式。 您甚至可以拥有商店的循环依赖关系
  • 没有_命名空间模块_。鉴于商店的扁平架构,“命名空间”商店是其定义方式所固有的,您可以说所有商店都是命名空间的。

有关如何将现有 Vuex ≤4 项目转换为使用 Pinia 的更详细说明,请参阅从 Vuex 迁移指南