# 介绍

**Pinia**最初是在2019年11月左右使用Composition API (opens new window)重新设计Vue Store的一个试验。从那时起,最初的原则仍然相同,但Pinia同时适用于Vue 2Vue 3,并且不要求您使用Composition API。除了安装和SSR之外,两者的API都是相同的,这些文档针对Vue 3,并在必要时提供有关Vue 2的注释,以便Vue 2Vue 3的用户都可以阅读!

# 为什么要使用 Pinia?

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

  • Devtools支持

    • 追踪actions, mutations的时间线
      • stores出现在使用它们的组件中
      • 时间旅行和更方便的调试
  • 热模块更新

    • 在不重新加载页面的情况下修改stores
      • 在开发过程中保持任何现有状态
  • 插件:使用插件扩展Pinia功能

  • JS用户提供适当的TypeScript支持或自动补全功能

  • 服务器端渲染支持

# 基础示例

这就是使用PiniaAPI方面的样子(请务必查看入门指南 (opens new window)中的完整说明)。首先创建一个 store

// 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辅助函数 (opens new window)。您也可以用同样的方式定义store,但是要使用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(西班牙语中的 “菠萝pineapple”)的一个有效的包名。事实上,菠萝是一群单独的花朵结合在一起,形成了多个果实的一种水果。与Stores类似,每个store都是独立生成的,但他们最终都是连接在一起的。菠萝也是一种原产于南美洲的美味热带水果。

# 一个更实际的示例

下面是一个更完整的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一起使用时,它提供了可靠的类型推断支持。

# RFCs

Vuex通过RFC收集尽可能多的社区反馈,而Pinia却没有。我根据自己开发应用程序、阅读其他人的代码、为使用Pinia的客户工作以及在Discord上回答问题的经验来测试想法。这使我能够提供一种适用于各种情况和应用程序大小的有效解决方案。我经常发布版本,并在保持其核心API不变的同时,使库不断发展。

# 与 Vuex 3.x/4.x 对比

Vuex 3.xVue 2VuexVuex 4.xVue 3Vuex

Pinia APIVuex ≤ 4有很大差异,如:

  • mutations不再存在。它们经常被认为非常啰嗦。它们最初带来了devtools的集成,但这不再是一个问题。

  • 无需创建复杂的自定义包装器来支持TypeScript,所有东西都是类型化的,并且API的设计也尽可能利用TS类型推断。

  • 无需额外的魔法字符串注入、引入函数和回调,享受自动完成的功能!

  • 无需动态添加Stores,默认情况下它们都是动态的,您甚至都不会注意到。

    注意,您仍然可以在需要时使用Store手动注册,但因为它是自动的,所以您无需担心后续。

  • 不再有模块的嵌套结构。您仍然可以通过在另一个store中引入和使用store来隐式嵌套store,但是Pinia在设计上提供了一个扁平的结构,同时仍然支持stores之间的交叉组合方式。你甚至可以有store的循环依赖关系。

  • 没有模块的命名空间。鉴于stores的扁平架构,“命名空间”的store与它们的定义方式是固有的,您可以说所有store都有命名空间的。

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