pinia中文文档 & 指导文档中文翻译版 & & pinia指导中文翻译

2023-05-16

Pinia 指导文档
中 文 翻 译 版

翻译者:jcLee95
Pinia 指导手册中文翻译地址(本文): https://blog.csdn.net/qq_28550263/article/details/120721160
Pinia API文档中文翻译地址(新): https://blog.csdn.net/qq_28550263/article/details/122601258

Pinia官网:https://pinia.esm.dev/
Vuex官网:https://vuex.vuejs.org/zh/


目录

1. Pinia介绍

  • 1.1 为什么我应该使用 Pinia?
  • 1.2 基本例子
  • 1.3 为什么是Pinia
  • 1.4 一个更接近实际的例子
  • 1.5 与 Vuex 对比

2. 核心概念

  • 2.1 定义一个 Store
    • 2.1.1 使用 store
  • 2.2 状态(State)
    • 2.2.1 访问状态
    • 2.2.2 重置状态
      • 2.2.2.1 使用 options API
    • 2.2.3 改变状态
    • 2.2.4 更换状态
    • 2.2.5 订阅状态
  • 2.3 访问器(Getters)
    • 2.3.1 访问其他 getter
    • 2.3.2 将参数传递给 getter
    • 2.3.3 访问其他store getters
    • 2.3.4 使用 setup() 的用法
    • 2.3.5 使用 options API 的用法
  • 2.4 动作(Actions)
    • 2.4.1 访问其他 存储(store)中的 action
    • 2.4.2 使用 setup() 时的用法
    • 2.4.3 使用 options API 时的用法
    • 2.4.4 订阅 action
  • 2.5 插件(Plugins)
    • 2.5.1 介绍
    • 2.5.2 存储(store)拓展
      • 2.5.2.1 添加新状态
    • 2.5.3 添加新的外部属性
    • 2.5.4 在插件内部调用$subscribe
    • 2.5.5 添加新 options
    • 2.5.6 TypeScript
      • 2.5.6.1 插件( plugins)类型注释
      • 2.5.6.2 存储属性(store properties)类型注释
      • 2.5.6.3 新状态(state)类型注释
      • 2.5.6.4 新选项(options)类型注释
    • 2.5.7 Nuxt.js

3. 服务端渲染 (Server Side Rendering,SSR)

  • 3.1 使用 setup() 之外的 store
  • 3.2 State hydration
  • 3.3 Nuxt.js
    • 3.3.1 安装
    • 3.3.2 在setup()外使用store
    • 3.3.3 在stores中使用Nuxt context
    • 3.3.4 将 Pinia 和 Vuex 一起使用
    • 3.3.5 Typescript

4. 热模块替换 (Hot Module Replacement, HMR)

5. 测试 stores

  • 5.1 Unit 测试一个 store
  • 5.2 Unit 测试模块
  • 5.3 E2E 测试

6. 不使用setup()时的用法

  • 6.1 允许访问整个 store
  • 6.2 TypeScript

7. 组合Stores

  • 7.1 嵌套 store
  • 7.2 Getters 共享
  • 7.3 动作(actions)共享

1. Pinia介绍

Pinia 起源于 2019年11月左右,尝试重新定义Vue中Store 和 组合式 API 联合使用时应该时什么样子。 从那以后,最初的原则始终保持一致, 但是 Pinia 同时对 Vue 2 以及 Vue 3同时有效, 并且并不要求你使用 API。 除了对于 安装过程服务端渲染(SSR),API 对两者来说都是一样的。本文档是针对 Vue 3 的,并在必要时带有有关Vue2的注释 ,以便使用Vue2和Vue3用户都可以阅读!

1.1 为什么我应该使用 Pinia?

Pinia 是一个 Vue 的存储库, 它能让你跨组件/页面共享状态.如果你熟悉组合式 API, 你大概会想到你已经能通过这样一个简单的例子来全局共享状态: export const state = reactive({}). 对于一个单页面应用(SPA, single page applications)来说的确如此,但是如果这是一个服务端渲染(SSR, server side rendered)应用时,将使你的应用暴露在安全漏洞之中。 但即使在小的 单页面应用(SPA, single page applications)中,你也可以通过使用Pinia获得很大便利:

  • Vue-Devtools 支持
    • 一个追踪动作、变化的时间轴
    • 出现在他们使用的组件中的存储Stores
    • 时间旅行和更简便的调试
  • 模块热替换
    • 在不要求重新加载页面条件下修改你的 stores
    • 当开发时保持所有存在的状态(state)
  • 插件: 使用插件扩展Pinia功能
  • 为JS用户提供适当的 TypeScript 支持与自动完成功能
  • 支持服务端渲染

1.2 基本例子

这就是使用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()) 来定义一个 Store :

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

  return { count, increment }
})

如果你至今仍不熟悉 setup() 和组合式 API, 别慌, Pinia 也可以提供一组 类似于Vuex中的map helpers . 你用同样的方法来定义stores,但是接下来使用的是 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']),
  },
}

你将在核心概念章节中找到关于每个map helper的更多信息。

1.3 为什么是Pinia

Pinia 是最接近 piña (西班牙语中的菠萝) 的单词,它是一个有效的包名。菠萝实际上是一组单个的花,它们结合在一起形成一个多重的果实。类似于stores,每一个都是单独诞生的,但最后都是连接在一起的。这也是原产于南美洲的美味热带水果。

1.4 一个更接近实际的例子

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

1.5 与 Vuex 对比

Pinia 尝试尽可能地接近Vuex的设计哲学。它旨在测试Vuex下一次迭代的提案,它是成功的,因为我们目前有一个Vuex 5的开放RFC,其 API 与Pinia使用的非常相似。请注意,Eduardo是Pinia的作者,是Vue.js核心团队的一员,积极参与Vue Router和Vuex等API的设计。Eduardo个人对这个项目的意图是重新设计使用全局 store 的体验,同时保持Vue平易近人的理念。Eduardo将Pinia的API保持得和Vuex一样紧密,因为它一直在向前发展,使人们可以很容易地迁移到Vuex,甚至在将来融合两个项目(在Vuex下)。

1.5.1 RFCs

虽然Vuex通过RFC从社区收集尽可能多的反馈,但Pinia没有。Eduardo根据自己开发应用程序、阅读他人代码和回答Discord问题的经验来测试想法。这使Eduardo能够提供一个可行的、经常发布的解决方案,并使它在人们使用它的同时不断发展,如果必要的话,可以在主要版本中进行重大变更(在第一次稳定发布后不太可能有重大的重大变更)。

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

Vuex 3.x 是基于 Vue 2 提供的Vuex, 而 Vuex 4.x 是 为Vue 3提供的Vuex。

Pinia API 与 Vuex ≤4有相当大的不同,即:

  • mutations(突变) 不再存在。他们经常被认为 极其 啰嗦. 他们最初带来了vue-devtools集成,但这不再是一个问题。
  • 不需要创建自定义的复杂包装器来支持TypeScript,一切都是类型化的,并且API的设计方式是尽可能利用TS类型推断。
  • 不再有魔术字符串注入,导入函数,调用它们,享受自动完成!
  • 不需要动态添加stores,默认都是动态的,你甚至不会注意到。请注意,您仍然可以随时手动使用stores来注册它,但是因为它是自动的,所以您不需要担心它。
  • 不再有 modules 的嵌套结构。你仍然可以通过在另一个store中导入和 using 一个store来隐式嵌套stores,但是Pinia提供了一种平面的设计结构,同时仍然支持store之间的交叉组合方式。
  • 没有 namespaced模块 。考虑到stores的平面结构(flat architecture),namespacing stores是定义store的固有方式,你可以说所有的stores都是namespacing

2. 核心概念

2.1 定义一个 Store

在深入研究核心概念之前,我们需要知道一个store(存储)是使用 defineStore()定义的, 并且它需要一个 唯一的 名字,作为第一个参数传递:

import { defineStore } from 'pinia'

// useStore 一切例如 useUser, useCart
// 第一个参数是应用程序中store的唯一id
export const useStore = defineStore('main', {
  // 其他选项...
})

这个name也称为id,它是必要的。Pinia使用它将store连接到devtools。命名返回的函数*use…*是跨成分的约定,使其用法习惯化。

2.1.1 使用 store

我们 定义 一个 store 因为直到在setup()内部调用“useStore()”之前, store并不会被创建:

import { useStore } from '@/stores/counter'

export default {
  setup() {
    const store = useStore()

    return {
      // 您可以返回整个存储实例,以便在模板中使用它
      store,
    }
  },
}

你可以定义任意多的stores你应该在不同的文件中定义每个store 以充分利用pinia(例如自动允许您的包进行代码拆分和TypeScript推理)。

如果你还没有使用setup组件,你仍然可以将 Pinia 与 map helpers 一起使用。

一旦存储被实例化,您就可以直接在存储上访在“state”、“getters”和“actions”中定义的任何属性。我们将在接下来的几页中详细介绍这些内容,但自动完成功能会对您有所帮助。

请注意,store是一个用reactive包装的对象,这意味着没有必要在getters之后写入.value,但是像setup中的props一样, 我们不能破坏它:

export default defineComponent({
  setup() {
    const store = useStore()
    // ❌ 这是行不通的,因为它破坏了响应性(reactivity)
    // 这和从`props`中破坏是一样的
    const { name, doubleCount } = store

    name // "eduardo"
    doubleCount // 2

    return {
      // will always be "eduardo"
      name,
      // will always be 2
      doubleCount,
      // this one will be reactive
      doubleValue: computed(() => store.doubleCount),
      }
  },
})

为了从store中提取属性,同时保持其反应性,您需要使用storeToRefs()。它将为任何响应性(reactivity)属性创建引用。当您只使用存储中(store)的状态,而不调用任何动作时,这很有用:

import { storeToRefs } from 'pinia'

export default defineComponent({
  setup() {
    const store = useStore()
    // `name` 和 `doubleCount` 是 reactive refs
    // 这也将为plugins(插件)添加的属性创建引用,但跳过任何动作或非反应(非ref/reactive)属性
    const { name, doubleCount } = storeToRefs(store)

    return {
      name,
      doubleCount
    }
  },
})

2.2 状态(State)

大多数时候,状态(State)是存储(store)的中心部分。人们通常从定义代表他们的应用程序的状态开始。在 Pinia 中,状态被定义为返回初始状态的函数。这允许 Pinia 在服务器端和客户端工作:

import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // 推荐用于全类型推理的箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断出它们的类型
      counter: 0,
      name: 'Eduardo',
      isAdmin: true,
    }
  },
})

技巧:

如果您使用 Vue 2,您在 state 中创建的数据遵循与 data 在 Vue 实例中 相同的规则,即 状态对象必须是普通的,并且您需要在向其添加新属性Vue.set()时调用

另请参阅: Vue#data

2.2.1 访问状态

默认情况下,您可以通过store实例访问状态来直接读取和写入状态:

const store = useStore()

store.counter++

2.2.2 重置状态

您可以通过调用store 上的方法将状态重置为其初始值$reset()

const store = useStore()

store.$reset()

2.2.2.1 使用 options API

如果您不使用 组合 API,而您正在使用computed, methods, …,则可以使用mapState()帮助器将状态属性映射为只读计算属性:

import { mapState } from 'pinia'

export default {
  computed: {
    // 在组件内允许访问 this.counter 
    // 与从 store.counter 读取一样
    ...mapState(useStore, ['counter'])
    // 与上面一样但是将注册它为 this.myOwnName
    ...mapState(useStore, {
      myOwnName: 'counter',
      // 你也可以写一个函数来访问 store
      double: store => store.counter * 2,
      // 它也能访问 `this` ,但是它不会正确地标注类型...
      magicValue(store) {
        return store.someGetter + this.counter + this.double
      },
    }),
  },
}
可修改状态

如果您希望能够写入这些状态属性(例如,如果您有一个表单),您可以mapWritableState()改用。请注意,您不能传递类似 with 的函数mapState()

import { mapWritableState } from 'pinia'

export default {
  computed: {
    // 允许访问组件内部的 this.counter,并允许设置它
    // this.counter++
    // 从 store.counter 中读取也一样
    ...mapWritableState(useStore, ['counter'])
    // 与上一样,但是将其注册为 this.myOwnName
    ...mapWritableState(useStore, {
      myOwnName: 'counter',
    }),
  },
}

技巧

您不需要 mapWritableState() 给像数组这样的集合,除非您将整个数组替换为cartItems = [],mapState()仍然允许您调用集合上的方法。

2.2.3 更改 state

除了直接用 store.counter++ 改变 store 之外,您还可以调用该$patch方法。它允许您对部分state对象同时应用多个更改:

store.$patch({
  counter: store.counter + 1,
  name: 'Abalam',
})

但是,使用这种语法应用某些 突变 确实很难或成本很高:任何集合修改(例如,从数组中推送、删除、拼接元素)都需要您创建一个新集合。正因为如此,此$patch方法还接受一个函数来对这种难以用 patch 对象 应用的突变进行分组:

cartStore.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

这里的主要区别是$patch()允许您将多个更改分组到 devtools 中的一个条目中。请注意**,直接更改state$patch()出现在 devtools 中,**并且可以穿越时间(在 Vue 3 中还没有)。

2.2.4 更换 state

您可以通过将 store$state属性设置为新对象来替换 store 的整个状态:

store.$state = { counter: 666, name: 'Paimon' }

您也可以通过更改替换您的应用程序的整体状态state中的pinia实例。这在SSR期间用于hydration。

pinia.state.value = {}

2.2.5 订阅状态

你可以通过$subscribe()store的方法观察状态及其变化,类似于 Vuex 的 subscribe 方法。$subscribe() 与常规相比使用的优点watch()订阅只会在 patch 后触发一次(例如,使用上面的函数版本时)。

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // 与 cartStore.$id 一样
  mutation.storeId // 'cart'
  // 仅当 mutation.type === 'patch object' 时可用
  mutation.payload // patch object passed to cartStore.$patch()

  // 每当状态改变时,将整个状态保存到本地存储中
  localStorage.setItem('cart', JSON.stringify(state))
})

默认情况下,状态订阅绑定到添加它们的组件(如果存储在组件的内部setup())。意思是,当组件被卸载时,它们将被自动删除。如果你想保持他们后成分是卸载,通过{ detached: true }作为第二个参数,以分离状态订阅从当前组件:

export default {
  setup() {
    const someStore = useSomeStore()

    // 该 subscription 将在组件卸载后保留
    someStore.$subscribe(callback, { detached: true })

    // ...
  },
}

技巧

您可以查看pinia实例上的整个状态:

watch(
 pinia.state,
 (state) => {
   // 每当状态改变时,将整个状态保存到本地存储中
   localStorage.setItem('piniaState', JSON.stringify(state))
 },
 { deep: true }
)

2.3 访问器(Getters)

Getter 完全等同于 Store 状态(state)的计算值。它们可以用 defineStore() 中的getters属性定义。其接收 state 作为第一个参数来鼓励使用箭头函数如:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
})

大多数时候,getter 只会依赖状态,但是,他们可能需要使用其他 getter。因此,我们可以在定义常规函数时访问整个 store 实例,但在 TypeScript 中需要定义返回类型的类型。这是由于TypeScript中的一个已知限制,不影响用箭头函数定义的getter,也不影响不使用 This 的getter:


export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // 自动地推断返回值类型为一个数
    doubleCount(state) {
      return state.counter * 2
    },
    //返回值类型 **必须** 被明确地指定
    doublePlusOne(): number {
      // 为整个存储(store)自动完成和类型注释 ✨
      return this.counter * 2 + 1
    },
  },
})

然后你可以直接在 store 实例上访问 getter:

<template>
  <p>Double count is {{ store.doubleCount }}</p>
</template>

<script>
export default {
  setup() {
    const store = useStore()

    return { store }
  },
}
</script>

2.3.1 访问其他 getter

与计算属性一样,您可以组合多个 getter。通过 this 访问任何其他 getter。即使您不使用 TypeScript,您也可以使用JSDoc提示您的 IDE 类型:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // 因为我们没有使用 `this`,故类型将被自动推断
    doubleCount: (state) => state.counter * 2,
    // 这里我们需要自己添加类型 (在 JS 中使用JSDoc)。 我们也可以用它来制作 getter文档。
    /**
     * 返回计数器值乘以二加一。
     *
     * @returns {number}
     */
    doubleCountPlusOne() {
      // autocompletion ✨
      return this.doubleCount + 1
    },
  },
})

2.3.2 将参数传递给 getter

Getter只是在幕后计算的属性,因此 不可能将任何参数传递给它们。但是您可以从getter返回一个函数以接受任何参数:

export const useStore = defineStore('main', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})

并在组件中使用:

<script>
export default {
  setup() {
    const store = useStore()

    return { getUserById: store.getUserById }
  },
}
</script>

<template>
User 2: {{ getUserById(2) }}
</template>

请注意,执行此动作时 getter 不再缓存,它们只是您调用的函数。但是您可以在 getter 本身内部缓存一些结果,这并不常见,但应该可证明性能更高:

export const useStore = defineStore('main', {
  getters: {
    getActiveUserById(state) {
      const activeUsers = state.users.filter((user) => user.active)
      return (userId) => activeUsers.find((user) => user.id === userId)
    },
  },
})

2.3.3 访问其他store getters

要使用另一个store getters,可以直接在getters内部使用:

import { useOtherStore } from './other-store'

export const useStore = defineStore('main', {
  state: () => ({
    // ...
  }),
  getters: {
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.localData + otherStore.data
    },
  },
})

2.3.4 使用 setup() 的用法

你可以直接作为 store 的属性访问任意 getter(与状态state属性完全相同):

export default {
  setup() {
    const store = useStore()

    store.counter = 3
    store.doubleCount // 6
  },
}

2.3.5 使用 options API 的用法

您可以使用前一部分在 state 中使用的相同的 mapState() 函数来映射到 getter:

import { mapState } from 'pinia'

export default {
  computed: {
    // 允许访问组件内部的 this.doubleCounter 
    // 与从 store.doubleCounter 中读取一样
    ...mapState(useStore, ['doubleCount'])
    // same as above but registers it as this.myOwnName
    ...mapState(useStore, {
      myOwnName: 'doubleCounter',
      // 你也可以写一个函数以访问 store
      double: store => store.doubleCount,
    }),
  },
}

2.4 动作(Actions)

动作(Actions)相当于组件中的**方法**。它们可以使用defineStore()的 actions属性进行定义,并且非常适合定义业务逻辑:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    },
    randomizeCounter() {
      this.counter = Math.round(100 * Math.random())
    },
  },
})

就像 getter一样,动作(Actions)可以通过 this 访问到整个存储实例,与完整的类型(和自动完成✨)的支持。与它们不同的是,actions可以是异步的(asynchronous),您可以 await 在它们内部进行任何 API 调用甚至其他操作!这是一个使用Mande的示例。请注意,您使用的库并不重要,只要您获得一个 Promise,您甚至可以使用本机fetch函数(仅限浏览器):

import { mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
  state: () => ({
    userData: null,
    // ...
  }),

  actions: {
    async registerUser(login, password) {
      try {
        this.userData = await api.post({ login, password })
        showTooltip(`Welcome back ${this.userData.name}!`)
      } catch (error) {
        showTooltip(error)
        // 让 form 组件展示 error
        return error
      }
    },
  },
})

你也可以完全自由地设置你想要的任何参数并返回任何东西。调用动作(Actions)时,一切都会自动推断!

动作像方法一样被调用:

export default defineComponent({
  setup() {
    const main = useMainStore()
    // 作为 store 的方法来调用 action
    main.randomizeCounter()

    return {}
  },
})

2.4.1 访问其他 存储(store)中的 action

要使用另一个store,您可以直接在 action 内部使用它

import { useAuthStore } from './auth-store'

export const useSettingsStore = defineStore('settings', {
  state: () => ({
    // ...
  }),
  actions: {
    async fetchUserPreferences(preferences) {
      const auth = useAuthStore()
      if (auth.isAuthenticated) {
        this.preferences = await fetchPreferences()
      } else {
        throw new Error('User must be authenticated')
      }
    },
  },
})

2.4.2 使用 setup() 时的用法

您可以直接调用任何操作作为 store 的方法:

export default {
  setup() {
    const store = useStore()

    store.randomizeCounter()
  },
}

2.4.3 使用 options API 时的用法

如果您不使用组合 API,而您正在使用computed, methods, …,则可以使用mapActions()帮助器将操作属性映射为组件中的方法:

import { mapActions } from 'pinia'

export default {
  methods: {
    // 组件内部允许访问 this.increment() 
    // 就像从 store.increment() 调用一样
    ...mapActions(useStore, ['increment'])
    // 与上面一样但是注册其为 this.myOwnName()
    ...mapActions(useStore, { myOwnName: 'doubleCounter' }),
  },
}

2.4.4 订阅 action

可以用 观察action及其结果store.$onAction()。传递给它的回调在操作本身之前执行。after处理承诺并允许您更改操作的返回值。onError允许您阻止错误传播。这些对于在运行时跟踪错误很有用,类似于Vue 文档中的这个技巧。

这是一个在运行操作之前和它们解决/拒绝之后记录的示例。

const unsubscribe = someStore.$onAction(
  ({
    name,    // 动作(action)名
    store,   // 存储实(store)例, 与 `someStore` 一样
    args,    // 传入该动作的一组参数
    after,   // 当 action 返回或者决定(resolves)后的钩子
    onError, // 当操作抛出或拒绝(rejects)时的钩子
  }) => {
    // 此特定操作调用的共享变量
    const startTime = Date.now()
    // 这将在执行`store action`之前触发
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    //  这将在`action` 成功(succeeds) 且完全运行后触发
    // it waits for any returned promised
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // 这将在`action`抛出错误或者返回一个拒绝的(rejects)`Promise`是触发
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// 手动移除监听器
unsubscribe()

默认情况下,操作订阅绑定到添加它们的组件(如果商店位于组件的 内部setup())。意思是,当组件被卸载时,它们将被自动删除。如果你想保持他们后成分是卸载,通过true作为第二个参数到分离action订阅*从当前组件:

export default {
  setup() {
    const someStore = useSomeStore()

    // this subscription will be kept after the component is unmounted
    someStore.$onAction(callback, true)

    // ...
  },
}

2.5 插件(Plugins)

Pinia 存储(store)可以通过 低级API 完全扩展。以下是你可以执行的 action:

  • 存储(store) 添加新属性;
  • 当定义 存储 时,添加新选项;
  • 存储 添加新方法;
  • 包装现有方法;
  • 更改甚至取消动作(action);
  • 执行如本地存储等副效果;
  • 仅适用于特定存储

插件将使用 添加到 pinia 实例 中。最简单的示例是通过返回对象向所有存储添加静态属性:pinia.use()

import { createPinia } from 'pinia'

// 给每个安装该插件后创建的 存储(store) 添加一个命名为 `secret` 的属性
// 这可能在不同的文件中
function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie' }
}

const pinia = createPinia()
// 将插件交给 pinia
pinia.use(SecretPiniaPlugin)

// 在另一个文件中
const store = useStore()
store.secret // 'the cake is a lie'

这对于添加全局对象(如 routermodalToast 管理器)非常有用。

2.5.1 介绍

Pinia 插件是一个 函数 ,options 返回要添加到存储(store)中的属性。它需要一个 options 参数,一个上下文

export function myPiniaPlugin(context) {
  context.pinia   // 使用 `createPinia()` 创建 pinia 
  context.app     // 使用 `createApp()` 创建当前的 app (仅 Vue)
  context.store   // 当前扩展插件的存储
  context.options // 传递给 `defineStore()` 的被定义存储的 options 对象 
  // ...
}

然后,此函数传递pinia.use()的结果 给 pinia

pinia.use(myPiniaPlugin)

插件只适用于 pinia传递到应用程序后创建的 存储(store)中,否则它们将不会被应用。

2.5.2 存储(store)拓展

您可以通过在插件中返回属性的对象来向每个存储添加属性:

pinia.use(() => ({ hello: 'world' }))

您也可以直接在 store上设置属性,但如果可能,请使用返回的版本,以便 devtools 可以自动地跟踪它们

pinia.use(({ store }) => {
  store.hello = 'world'
})

插件返回的任何属性都会被 devtools 自动跟踪,因此为了在 devtools 中可见,请确保仅在要在 devtools 中调试它时才将其添加到 dev 模式下:hello``store._customProperties

// 从上面的例子
pinia.use(({ store }) => {
  store.hello = 'world'
  // 确保你的 bundler 能处理它。 默认情况下,webpack 和 vite 应该这样做
  if (process.env.NODE_ENV === 'development') {
    // 添加你在存储(store)中设置的任何键(key)
    store._customProperties.add('hello')
  }
})

注意每个存储(store)都使用了reactive 进行包装,自动地包装任意Ref (, , ...),包括了:ref() computed()

const sharedRef = ref('shared')
pinia.use(({ store }) => {
  // 每个 store 都有自己的 `hello` 属性
  store.hello = ref('secret')
  // 它会自动解包装
  store.hello // 'secret'

  // 所有的 stores 共享值 `shared` 属性
  store.shared = sharedRef
  store.shared // 'shared'
})

这就是您可以访问所有计算属性的原因,而无需访问它们,以及为什么它们是响应式的.value

2.5.2.1 添加新状态

如果要向存储添加新的状态属性或要在水化期间使用的属性,则必须在两个位置添加它

  • 因为在store中,所以你可以访问它store.myState
  • store.$state上,因此它可以在devtools中使用,并在SSR期间序列化

请注意,这允许您共享或属性:ref``computed

const globalSecret = ref('secret')
pinia.use(({ store }) => {
  // `secret` 被共享到所有的 stores 中
  store.$state.secret = globalSecret
  store.secret = globalSecret
  // 它会自动解包
  store.secret // 'secret'

  const hasError = ref(false)
  store.$state.hasError = hasError
  // this one must always be set
  store.hasError = toRef(store.$state, 'hasError')

  // in this case it's better not to return `hasError` since it
  // will be displayed in the `state` section in the devtools
  // anyway and if we return it, devtools will display it twice.
})

警告

如果您使用的是Vue 2,Pinia 会受到与 Vue相同的 reactivity 警告。在创建新的状态属性时,您需要使用 from 和:set @vue/composition-api secret hasError

import { set } from '@vue/composition-api'
pinia.use(({ store }) => {
 if (!store.$state.hasOwnProperty('hello')) {
   const secretRef = ref('secret')
   // If the data is meant to be used during SSR, you should
   // set it on the `$state` property so it is serialized and
   // picked up during hydration
   set(store.$state, 'secret', secretRef)
   // set it directly on the store too so you can access it
   // both ways: `store.$state.secret` / `store.secret`
   set(store, 'secret', secretRef)
   store.secret // 'secret'
 }
})

2.5.3 添加新的外部属性

在添加外部属性、来自其他库的类实例或只是非响应性内容时,在将对象传递到 pinia 之前,应先将其包装起来。下面是将路由器添加到每个存储(store)的示例:markRaw()

import { markRaw } from 'vue'
// 基于你的 router 在哪进行调整
import { router } from './router'

pinia.use(({ store }) => {
  store.router = markRaw(router)
})

2.5.4 在插件内部调用$subscribe

您也可以在插件中使用store.KaTeX parse error: Expected 'EOF', got '#' at position 60: …epts/state.html#̲subscribing-to-…onAction:

pinia.use(({ store }) => {
  store.$subscribe(() => {
    // react to store changes
  })
  store.$onAction(() => {
    // react to store actions
  })
})

2.5.5 添加新 options

在定义存储时可以创建新 options,以便以后从插件中使用它们。例如,您可以创建一个 debounce 选项来取消对任何 动作(action) 的debounce:

defineStore('search', {
  actions: {
    searchContacts() {
      // ...
    },
  },

  // 这在接下来将被一个插件读取
  debounce: {
    // debounce the action searchContacts by 300ms
    searchContacts: 300,
  },
})

然后,插件可以读取该选项以包装动作(action)并替换原始动作:

// 使用任何 debounce 库
import debounce from 'lodash/debunce'

pinia.use(({ options, store }) => {
  if (options.debounce) {
    // 我们正在用新的 动作(actions) 取代旧的动作
    return Object.keys(options.debounce).reduce((debouncedActions, action) => {
      debouncedActions[action] = debounce(
        store[action],
        options.debounce[action]
      )
      return debouncedActions
    }, {})
  }
})

请注意,使用安装程序语法时,自定义选项将作为第 3 个参数传递:

defineStore(
  'search',
  () => {
    // ...
  },
  {
    // 这将接下来被一个插件读取
    debounce: {
      // debounce the action searchContacts by 300ms
      searchContacts: 300,
    },
  }
)

2.5.6 TypeScript

上面显示的所有内容都可以通过类型支持来完成,因此您永远不需要使用 any@ts-ignore

2.5.6.1 插件( plugins)类型注释

Pinia插件可以按如下方式注释类型:

import { PiniaPluginContext } from 'pinia'

export function myPiniaPlugin(context: PiniaPluginContext) {
  // ...
}

2.5.6.2 存储属性(store properties)类型注释

向存储区添加新属性时,还应扩展 PiniaCustomProperties 接口。

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties {
    // 通过使用一个 setter 我们能允许 strings and refs
    set hello(value: string | Ref<string>)
    get hello(): string

    // 您也可以定义更简单的值
    simpleNumber: number
  }
}

然后可以安全地编写和读取它:

pinia.use(({ store }) => {
  store.hello = 'Hola'
  store.hello = ref('Hola')

  store.number = Math.random()
  // @ts-expect-error: we haven't typed this correctly
  store.number = ref(Math.random())
})
PiniaCustomProperties`是允许您引用存储的属性的泛型类型。想象一下下面的示例,其中我们复制初始选项为(这仅适用于选项存储):`$options
pinia.use(({ options }) => ({ $options: options }))

我们可以通过使用以下4种泛型类型来正确注释它的类型:PiniaCustomProperties

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties<Id, S, G, A> {
    $options: {
      id: Id
      state?: () => S
      getters?: G
      actions?: A
    }
  }
}

技巧

在泛型中扩展类型时,它们的命名必须与源代码中的完全一样。Id 不能命名为 idIS 不能命名为 State。以下是每个字母的含义:

  • S: State
  • G: Getters
  • A: Actions
  • SS: Setup Store / Store

2.5.6.3 新状态(state)类型注释

当添加新的状态属性(storestore.$state )时,您需要添加类型到PiniaCustomStateProperties来代替。与 PiniaCustomProperties 不同的是,它只接收 State 泛型:

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomStateProperties<S> {
    hello: string
  }
}

2.5.6.4 新 options 类型注释

在为 defineStore() 创建新 option 时,您应该扩展DefineStoreOptionsBase. 与 PiniaCustomProperties 不同的是,它只公开了两个泛型:StateStore 类型,以允许您限制可以定义的内容。例如,您可以使用动作(action)的名称:

import 'pinia'

declare module 'pinia' {
  export interface DefineStoreOptionsBase<S, Store> {
    // 允许为 ms 的任意动作(actions)定义一个数
    debounce?: Partial<Record<keyof StoreActions<Store>, number>>
  }
}

提示

还有一个类型用于从 Store 类型中提取getter
您还可以 通过分别扩展类型DefineStoreOptionsDefineSetupStoreOptions 来扩展setup storesoption stores

2.5.7 Nuxt.js

当使用pinia与Nuxt一起使用时,您必须首先创建一个Nuxt插件。这将为您提供对实例的访问权限:pinia

// plugins/myPiniaPlugin.js
import { PiniaPluginContext } from 'pinia'
import { Plugin } from '@nuxt/types'

function MyPiniaPlugin({ store }: PiniaPluginContext) {
  store.$subscribe((mutation) => {
    // 相应到存储(store)的改变
    console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`)
  })

  return { creationTime: new Date() }
}

const myPlugin: Plugin = ({ pinia }) {
  pinia.use(MyPiniaPlugin);
}
export default myPlugin

请注意,上面的示例使用了 TypeScript。如果您使用的是文件,则必须删除类型注释及其导入(import)。PiniaPluginContext Plugin .js

3. 服务端渲染 (Server Side Rendering,SSR)

注意

如果你使用的是 Nuxt.js, 则需要阅读 these instructions

只要您在setup函数、gettersactions的顶部调用useStore()函数,使用Pinia创建stores,就可以进行SSR了:

export default defineComponent({
  setup() {
    // this works because pinia knows what application is running inside of
    // `setup()`
    const main = useMainStore()
    return { main }
  },
})

3.1 使用 setup() 之外的 store

如果您需要在其他地方使用 store,您需要将传递给应用程序的pinia实例传递给useStore()函数来调用:

const pinia = createPinia()
const app = createApp(App)

app.use(router)
app.use(pinia)

router.beforeEach((to) => {
  // ✅ 这将确保正确的store用于当前运行的应用程序
  const main = useMainStore(pinia)

  if (to.meta.requiresAuth && !main.isLoggedIn) return '/login'
})

Pinia 方便地将自己作为 $pinia 添加到您的应用程序中,这样您就可以在像 serverPrefetch()这样的功能中使用它:

export default {
  serverPrefetch() {
    const store = useStore(this.$pinia)
  },
}

3.2 State hydration

To hydrate the initial state,你需要确保rootState包含在HTML中的某个地方以便Pinia稍后获取它。根据您使用的SSR,出于安全原因,你应跳过这个状态。我们推荐使用Nuxt.js使用的那一个 @nuxt/devalue :

import devalue from '@nuxt/devalue'
import { createPinia } from 'pinia'
// 检索 rootState 服务器端
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)

// 呈现页面后,根状态建立并可以直接在`pinia.state.value`上读取。

// serialize, escape (如果用户可以更改状态的内容,这一点非常重要,几乎总是如此), 
// 并将其放在页面的某个地方,例如作为一个全局变量。
devalue(pinia.state.value)

取决于您使用什么进行SSR,您将设置一个 初始状态 变量,该变量将在HTML中序列化。你也应该保护自己免受XSS袭击。 例如:使用vite-ssr 你可以使用 transformState option 和@nuxt/devalue

import devalue from '@nuxt/devalue'

export default viteSSR(
  App,
  {
    routes,
    transformState(state) {
      return import.meta.env.SSR ? devalue(state) : state
    },
  },
  ({ initialState }) => {
    // ...
    if (import.meta.env.SSR) {
      // 这将被字符串化并设置为“window.__INITIAL_STATE__”
      initialState.pinia = pinia.state.value
    } else {
      // 在客户端,我们恢复状态
      pinia.state.value = initialState.pinia
    }
  }
)

你能使用 其他选择 来 @nuxt/devalue ,取决于你需要什么,例如如果可以用 JSON.stringify()/JSON.parse()序列化和解析您的状态, 可以大大提高你的性能.

根据您的环境调整此策略。在客户端调用任何 useStore() 函数之前,请确保hydrate pinia的状态。例如,如果我们将状态序列化为一个 <script>标签,使其在客户端可以通过window.__pinia进行全局访问,我们可以这样写:

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

// 必须由用户设置
if (isClient) {
  pinia.state.value = JSON.parse(window.__pinia)
}

3.3 Nuxt.js

将Pinia与Nuxt.js一起使用更容易,因为Nuxt在服务器端渲染时会处理很多事情。例如,你不需要关心序列化或XSS攻击

3.3.1 安装

确保安装 @nuxtjs/composition-apipinia一起安装:

yarn add pinia @pinia/nuxt @nuxtjs/composition-api
# or with npm
npm install pinia @pinia/nuxt @nuxtjs/composition-api

我们提供了一个module来为您处理一切,您只需要将它添加到nuxt.config.js文件中的 buildModules中:

// nuxt.config.js
export default {
  // ... other options
  buildModules: [
    // https://composition-api.nuxtjs.org/getting-started/setup#quick-start
    '@nuxtjs/composition-api/module',
    '@pinia/nuxt',
  ],
}

就这样,像往常一样使用你的store

3.3.2 在setup()外使用store

如果你打算在 setup()外面定义一个 store ,必须通过 pinia 对象给 useStore()。我们将它添加到上下文中,因此您可以在 asyncData()fetch()中访问它:

import { useStore } from '~/stores/myStore'

export default {
  asyncData({ pinia }) {
    const store = useStore(pinia)
  },
}

3.3.3 在stores中使用Nuxt context

您也可以通过使用注入的属性$nuxt在任何store 中使用上下文(context) :

import { useUserStore } from '~/stores/userStore'

defineStore('cart', {
  actions: {
    purchase() {
      const user = useUserStore()
      if (!user.isAuthenticated()) {
        this.$nuxt.redirect('/login')
      }
    },
  },
})

3.3.4 将 Pinia 和 Vuex 一起使用

建议避免同时使用pinia和Vuex,但如果您需要同时使用两者,则需要告诉Pinia不要禁用它:

// nuxt.config.js
export default {
  buildModules: [
    '@nuxtjs/composition-api/module',
    ['@pinia/nuxt', { disableVuex: false }],
  ],
  // ... other options
}

3.3.5 Typescript

如果您正在使用 TypeScript 或拥有 jsconfig.json,您还应该为 context.pinia添加类型:

{
  "types": [
    // ...
    "@pinia/nuxt"
  ]
}

这也将确保您具有自动完成功能 😉 .

4. 热模块替换 (Hot Module Replacement, HMR)

Pinia支持Hot Module替换,因此您可以编辑您的 stores并在应用程序中直接与它们交互,而无需重新加载页面,从而允许您保持现有 state(状态)。添加甚至删除stateactionsgetters.

目前,只有Vite得到官方支持,但任何实现import.meta.hot 规范的bundler都应该可以工作(例如 webpack似乎使用import.meta.webpackHot而不是import.meta.hot)。您需要在任何存储声明旁边添加这段代码。假设您有三个存储:auth.js, cart.js,和 chat.js,在创建store definition后,您必须添加(并修改)此内容:

// auth.js
import { defineStore, acceptHMRUpdate } from 'pinia'

const useAuth = defineStore('auth', {
  // options...
})

// 在这种情况下,确保传递正确的store定义`useAuth`。
if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot))
}

5. 测试 stores

根据设计,Stores将在许多地方使用,这可能会使测试变得更加困难。幸运的是,情况并非一定如此。在测试 store 时,我们需要注意三件事:

  • pinia 实例: Stores 的工作不能没有它
  • actions:大多数时候,它们包含了我们stores最复杂的逻辑。如果他们被default mocked 岂不是很好?
  • 插件(Plugins): 如果你依赖插件,你也必须安装它们进行测试

根据你测试的内容或方式,我们需要以不同的方式处理这三个问题:

  • 单元测试 stores (组件外)
  • 使用stores单元测试组件
  • 端到端测试

5.1 Unit 测试一个 store

要对一个store进行单元测试,最重要的部分是创建一个pinia实例:

// counterStore.spec.ts
import { setActivePinia, createPinia } from 'pinia'
import { useCounter } from '../src/stores/counter'

describe('Counter Store', () => {
  beforeEach(() => {
    // 创建一个新的pinia并激活它,这样它就可以被任何`useStore()`调用自动拾取,而无需传递给它:
    // `useStore(pinia)`
    setActivePinia(createPinia())
  })

  it('increments', () => {
    const counter = useCounter()
    expect(counter.n).toBe(0)
    counter.increment()
    expect(counter.n).toBe(1)
  })

  it('increments by amount', () => {
    const counter = useCounter()
    counter.increment(10)
    expect(counter.n).toBe(10)
  })
})

如果你有任何store插件,有一件重要的事情要知道:plugins won’t be used until pinia is installed in an App. 这可以通过创建一个空的或假的应用程序来解决:

import { setActivePinia, createPinia } from 'pinia'
import { createApp } from 'vue'
import { somePlugin } from '../src/stores/plugin'

// 与上面相同的代码...

// 你不需要为每个测试创建一个应用程序
const app = createApp({})
beforeEach(() => {
  const pinia = createPinia().use(somePlugin)
  app.use(pinia)
  setActivePinia(pinia)
})

5.2 Unit 测试模块

这可以通过createTestingPinia()实现。我还没有能够为此编写适当的文档,但可以通过自动完成和工具提示中显示的文档来发现它的用法。

首先安装 :@pinia/testing
npm i -D @pinia/testing

并确保在安装组件时在测试中 创建测试 pinia:

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'

const wrapper = mount(Counter, {
  global: {
    plugins: [createTestingPinia()],
  },
})

const store = useSomeStore() // 使用测试pinia!

// 状态(state)可以被直接操纵
store.name = 'my new name'
// 也可以通过 patch 来完成
store.$patch({ name: 'new name' })
expect(store.name).toBe('new name')


// 默认情况下,动作(actions)是存根化的,但是
// 可以通过将一个 option 传递给`createTestingPinia()`来进行配置
store.someAction()

expect(store.someAction).toHaveBeenCalledTimes(1)
expect(store.someAction).toHaveBeenLastCalledWith()

5.3 E2E 测试

说到 pinia,你不需要为e2e测试改变什么,这就是e2e测试的全部意义!您也许可以测试HTTP请求,但这远远超出了本指南的范围 😄.

6. 不使用setup()时的用法

即使你不适用组合式API,也可以使用Pinia(尽管如果您正在使用Vue 2,您仍然需要安装@vue/composition-api 插件)。然而我们建议尝试一下组合式API并学习它,但现在可能还不是您和您的团队的时候,您可能正在迁移应用程序,或者有任何其他原因。有几个功能:

  • mapStores
  • mapState
  • mapWritableState
  • ⚠️ mapGetters (只是为了方便迁移,请改用mapState()
  • mapActions

6.1 允许访问整个 store

如果您需要访问store中的几乎所有内容,映射store的每个属性可能太多了… 相反,您可以使用mapStores()访问整个store

import { mapStores } from 'pinia'

// 给定两个具有以下id的stores
const useUserStore = defineStore('user', {
  // ...
})
const useCartStore = defineStore('cart', {
  // ...
})

export default {
  computed: {
    // 注意,我们没有传递数组,只是一个接一个地传递一个`store`,每个存储都可以作为其id + 'Store'来访问
    ...mapStores(useCartStore, useUserStore),
    }),
  },

  methods: {
    async buyStuff() {
      // 在任何地方使用它们!
      if (this.userStore.isAuthenticated()) {
        await this.cartStore.buy()
        this.$router.push('/purchased')
      }
    },
  },
}

默认情况下,Pinia将为每个store的id添加"Store"后缀。您可以通过调用 setMapStoreSuffix()来自定义此行为:

import { createPinia, setMapStoreSuffix } from 'pinia'

// 完全删除后缀: this.user, this.cart
setMapStoreSuffix('')
// this.user_store, this.cart_store (没关系,我不会评论你)
setMapStoreSuffix('_store')
export const pinia = createPinia()

6.2 TypeScript

默认情况下, 所有 map helpers 都支持自动完成,你不需要做任何事情。 你可以调用setMapStoreSuffix() 来更改"Store"后缀,您还需要将其添加到TS文件或您的global.d.ts文件中的某个位置。最方便的地方就是你调用setMapStoreSuffix()的相同位置:

import { createPinia, setMapStoreSuffix } from 'pinia'

setMapStoreSuffix('') // 完全去掉后缀
export const pinia = createPinia()

declare module 'pinia' {
  export interface MapStoresCustomization {
    // 将其设置为与上面相同的值
    suffix: ''
  }
}

警告

如果你使用一个 TypeScript 声明文件 (如 global.d.ts),确保 import 'pinia' 在顶部以显示所有现有类型。

7. 组合Stores

组合 stores 就是让 store互相使用,有一个规则需要遵守:

如果 两个或者多个stores互用彼此,它们不能通过getter或action创建无限循环。他们都不能在他们的 setup 函数中直接读取彼此的状态:

const useA = defineStore('a', () => {
  const b = useB()

  // ❌ 这是不可行的,因为 b 也试图读取 a.name
  b.name

  function doSomething() {
    // ✅ 在 computed 或者 actions 中读取 b 属性
    const bName = b.name
    // ...
  }

  return {
    name: ref('I am A'),
  }
})

const useB = defineStore('b', () => {
  const a = useA()

  // ❌ 这是不可行的,因为 a 也试图读取 a.name
  a.name

  function doSomething() {
    // ✅ 在 computed 或者 actions 中读取 b 属性
    const aName = a.name
    // ...
  }

  return {
    name: ref('I am B'),
  }
})

7.1 嵌套 store

注意,如果一个 store 使用另一个 store ,则无需在单独的文件中创建新的 store。您可以直接导入,把它想象成嵌套。

您可以在任何 getteraction 顶部调用useOtherStore()

import { useUserStore } from './user'

export const cartStore = defineStore('cart', {
  getters: {
    // ... 其它 getters
    summary(state) {
      const user = useUserStore()

      return `Hi ${user.name}, you have ${state.list.length} items in your cart. It costs ${state.price}.`
    },
  },

  actions: {
    purchase() {
      const user = useUserStore()

      return apiPurchase(user.id, this.list)
    },
  },
})

7.2 Getters 共享

您可以简单地在getter内部调用useOtherStore()

import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', {
  getters: {
    summary(state) {
      const user = useUserStore()

      return `Hi ${user.name}, you have ${state.list.length} items in your cart. It costs ${state.price}.`
    },
  },
})

7.3 动作(actions)共享

同理也可用于 actions的共享:

import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', {
  actions: {
    async orderCart() {
      const user = useUserStore()

      try {
        await apiOrderCart(user.token, this.items)
        // another action
        this.emptyCart()
      } catch (err) {
        displayError(err)
      }
    },
  },
})

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

pinia中文文档 & 指导文档中文翻译版 & & pinia指导中文翻译 的相关文章

随机推荐

  • 什么是“约瑟夫环”

    今天遇到一个关于 约瑟夫环 的问题 xff0c 于是上网查了下什么是 约瑟夫环 出自百度 xff1a 约瑟夫问题 xff08 有时也称为约瑟夫斯置换 xff0c 是一个出现在计算机科学和数学中的问题 在计算机编程的算法中 xff0c 类似问
  • 使用Fontcreator字体制作软件及字体设计学习

    fontcreator对于字体修改爱好者而言是一款极好的文字编辑工具 xff0c 门槛要求低 专业性强 集设计和修改为一体 xff0c 可用来制作 编辑 修改ttf xff0c otf xff0c ttc格式的字体文件 xff0c 非常的实
  • 远程连接centos 7 图形化桌面

    使用xrdp工具 xff0c 类似windows系统的远程桌面 xff08 rdp xff09 xff0c 需要在服务端安装远程桌面服务 如果你的服务器可以使用阿里的yum源 xff0c 可以直接使用epel仓库安装 xff0c 执行以下命
  • selinux - Android编写sepolicy

    为service编写sepolicy 由init启动的service服务要在各自的selinux domain中运行 具体flow如下 init devices rc中声明service xff0c 将在init时启动 xff1a Note
  • Spring Boot结合easyExcel实现自增序号

    有些业务功能要求能导出序号 xff0c 现在提供两种实现方式 通过mysql查询实现 xff1b 实现RowWriteHandler接口 xff0c 自定义拦截器 xff1b 一 通过mysql查询实现 通过自定义变量实现每行数据 43 1
  • Spring Boot 实体里的List集合参数验证

    Spring Boot 实体里的List集合参数验证 Spring Boot 通过Hibernate Validator 加验证大家都知道 不知道的话可以参考这篇文章SpringBoot里参数校验 参数验证 今天讲一下Spring Boot
  • 树形结构工具类,如:菜单、部门等

    1 树节点 span class token keyword package span span class token namespace com span class token punctuation span zjson span
  • EasyPoiUtil导出工具

    span class token keyword package span span class token namespace com span class token punctuation span zjson span class
  • ffmpeg部署和springboot使用

    视频存储部署 一 环境安装 1 1 yasm安装 在编译ffmpeg时 xff0c 执行 configure xff0c 报出了如下错误 xff1a nasm yasm not found or too old Use disable x8
  • 【docker】安装clickhouse

    一 联网安装clickhouse 1 为了方便安装 xff0c 将clickhouse的工作目录和数据目录都在同一个目录下 xff0c 在home下创建目录 mkdir clickhouse cd clickhouse 创建日志 配置文件
  • springboot整合mybatis-plus、clickhouse、mysql多数据源

    springboot的多数据源有多种方式 xff0c 本文按照指定不同dao mapper xml的方式来实现访问不同的数据源 这样的好处是不用注解去切换数据源 1 引入驱动 span class token generics span c
  • linux中的oracle启动和关闭

    一 启动数据库实例 span class token number 1 span 切换到oracle用户 su span class token operator span oracle span class token number 2
  • Source Insight 中文注释为乱码解决办法(完美解决,一键搞定)

    我从网上查了一堆解决办法 xff0c 但是都是2017年以前的解决方案 xff0c 并且都是针对于source insight 3 5及以下版本的 xff0c 目前SI软件版本都到4 0了 xff0c 应该有新方法出现了 干货 xff1a
  • BigDecimal计算工具类

    方便以后大家使用 span class token keyword import span span class token namespace java span class token punctuation span math spa
  • 安装、使用mongodb踩过的坑

    轻松一下 没用分布式架构之前 xff0c 你只有一个问题 xff1a 并发性能不足 用了分布式架构 xff0c 多出了一堆问题 xff1a 数据如何同步 主键如何产生 如何熔断 分布式事务如何处理 使用mongodb踩过的坑 今天对安装 x
  • JS使用总结:简单标签赋值和取值:span等;实时触发事件

    JS使用总结 简单标签赋值和取值 xff1a span等 xff1b 实时触发事件 xff1b 了解一下简单标签赋值和取值实时触发事件代码 了解一下 作为后台开发 xff0c 今天使用了前端的技术 xff0c 做一个总结 xff1b 包括两
  • 自定义生产省略号

    span class hljs attribute overflow span span class hljs value hidden span span class hljs attribute text overflow span s
  • android WIFI相关问题集合

    1 Wi Fi hotspot cannot be used normally 问题描述 xff1a android P Settings Network amp internet Hotspot amp tethering Wi Fi h
  • MongoDB Shell工具:mongosh的使用

    MongoDB Shell工具 xff1a mongosh的使用 jcLee 的 CSDN 博客 邮箱 xff1a 291148484 64 163 com CSDN 主页 xff1a https blog csdn net qq 2855
  • pinia中文文档 & 指导文档中文翻译版 & & pinia指导中文翻译

    Pinia 指导文档 中 文 翻 译 版 翻译者 xff1a jcLee95 Pinia 指导手册中文翻译地址 xff08 本文 xff09 xff1a https blog csdn net qq 28550263 article det