text: '对本页提出修改建议',
},
- outlineTitle: '本页内容',
+ outline: {
+ label: '本页内容'
+ },
nav: [
// { text: 'Config', link: '/config/' },
action 执行完的钩子。
它接收 action 的返回值,如果是 Promise,它将被自动解包。
-##### 参数{#parameters}
+##### 参数 %{#parameters}%
| 名称 | 类型 |
| :------ | :------ |
| :------ |
| `S` |
-#### 类型声明{#type-declaration}
+#### 类型声明 %{#type-declaration}%
▸ (`mutation`, `state`): `void`
___
-### \_ExtractGettersFromSetupStore{#extractgettersfromsetupstore}
+### \_ExtractGettersFromSetupStore %{#extractgettersfromsetupstore}%
Ƭ **\_ExtractGettersFromSetupStore**<`SS`\>: `SS` extends `undefined` \| `void` ? {} : [`_ExtractGettersFromSetupStore_Keys`](pinia.md#_extractgettersfromsetupstore_keys)<`SS`\> extends keyof `SS` ? `Pick`<`SS`, [`_ExtractGettersFromSetupStore_Keys`](pinia.md#_extractgettersfromsetupstore_keys)<`SS`\>\> : `never`
___
-### \_ExtractStateFromSetupStore{#extractstatefromsetupstore}
+### \_ExtractStateFromSetupStore %{#extractstatefromsetupstore}%
Ƭ **\_ExtractStateFromSetupStore**<`SS`\>: `SS` extends `undefined` \| `void` ? {} : [`_ExtractStateFromSetupStore_Keys`](pinia.md#_extractstatefromsetupstore_keys)<`SS`\> extends keyof `SS` ? [`_UnwrapAll`](pinia.md#_unwrapall)<`Pick`<`SS`, [`_ExtractStateFromSetupStore_Keys`](pinia.md#_extractstatefromsetupstore_keys)<`SS`\>\>\> : `never`
___
-### \_ExtractStateFromSetupStore\_Keys{#extractstatefromsetupstore-keys}
+### \_ExtractStateFromSetupStore\_Keys %{#extractstatefromsetupstore-keys}%
Ƭ **\_ExtractStateFromSetupStore\_Keys**<`SS`\>: keyof { [K in keyof SS as SS[K] extends \_Method \| ComputedRef ? never : K]: any }
| :------ |
| `SS` |
-## 变量{#variables}
+## 变量 %{#variables}%
### PiniaVuePlugin %{#piniavueplugin}%
默认为`"Store"`。如果你需要使用 TypeScript,
请确保扩展 MapStoresCustomization 接口。
-#### 参数{#parameters}
+#### 参数 %{#parameters}%
| 名称 | 类型 | 描述 |
| :------ | :------ | :------ |
添加于 [2.0.0-rc.0](https://github.com/vuejs/pinia/blob/v2/packages/pinia/CHANGELOG.md#200-rc0-2021-07-28)
-用 `StoreGeneric` 取代 `GenericStore` 类型的全部用法。这是新的通用 store 类型,应该可以接受任何类型的 store。如果你在写函数时使用 `Store` 类型而不想传递其泛型(例如`Store<Id, State, Getters, Actions>`),你可以使用 `StoreGeneric`,因为没有泛型的 `Store` 类型会创建一个空的 store 类型:
+用 `StoreGeneric` 取代 `GenericStore` 类型的全部用法。这是新的通用 store 类型,应该可以接受任何类型的 store。如果你在写函数时使用 `Store` 类型而不想传递其泛型 (例如`Store<Id, State, Getters, Actions>`),你可以使用 `StoreGeneric`,因为没有泛型的 `Store` 类型会创建一个空的 store 类型:
-```diff
--function takeAnyStore(store: Store) {}
-+function takeAnyStore(store: StoreGeneric) {}
-
--function takeAnyStore(store: GenericStore) {}
-+function takeAnyStore(store: StoreGeneric) {}
+```ts
+function takeAnyStore(store: Store) {} // [!code --]
+function takeAnyStore(store: StoreGeneric) {} // [!code ++]
+function takeAnyStore(store: GenericStore) {} // [!code --]
+function takeAnyStore(store: StoreGeneric) {} // [!code ++]
```
## 针对插件的 `DefineStoreOptions` %{#definestoreoptions-for-plugins}%
如果你在用 TypeScript 写插件并扩展了 `DefineStoreOptions` 类型来添加自定义选项,你应该把它改名为 `DefineStoreOptionsBase`。这个类型将同时适用于 setup 和 option store。
-```diff
- declare module 'pinia' {
-- export interface DefineStoreOptions<S, Store> {
-+ export interface DefineStoreOptionsBase<S, Store> {
- debounce?: {
- [k in keyof StoreActions<Store>]?: number
- }
- }
- }
+```ts
+declare module 'pinia' {
+ export interface DefineStoreOptions<S, Store> { // [!code --]
+ export interface DefineStoreOptionsBase<S, Store> { // [!code ++]
+ debounce?: {
+ [k in keyof StoreActions<Store>]?: number
+ }
+ }
+}
```
## `PiniaStorePlugin` 已被重命名 %{#piniastoreplugin-was-renamed}%
类型 `PiniaStorePlugin` 被重新命名为 `PiniaPlugin`。
-```diff
--import { PiniaStorePlugin } from 'pinia'
-+import { PiniaPlugin } from 'pinia'
-
--const piniaPlugin: PiniaStorePlugin = () => {
-+const piniaPlugin: PiniaPlugin = () => {
- // ...
- }
+```ts
+import { PiniaStorePlugin } from 'pinia' // [!code --]
+import { PiniaPlugin } from 'pinia' // [!code ++]
+const piniaPlugin: PiniaStorePlugin = () => { // [!code --]
+const piniaPlugin: PiniaPlugin = () => { // [!code ++]
+ // ...
+}
```
**注意这个更新只能在升级到最新的没有弃用的 Pinia 版本后生效**。
## 支持 webpack 4 %{#webpack-4-support}%
-如果你使用的是 webpack 4(Vue CLI 使用的是 webpack 4),你可能会遇到这样的错误:
+如果你使用的是 webpack 4 (Vue CLI 使用的是 webpack 4),你可能会遇到这样的错误:
```
ERROR Failed to compile with 18 errors
如果你正在使用 Nuxt,pinia 现在有了专门的 Nuxt 软件包🎉。请用以下方法安装它:
-```shell
+```bash
npm i @pinia/nuxt
# 或者使用 yarn
yarn add @pinia/nuxt
如果你使用 TypeScript,还要调整你的 `nuxt.config.js` 和 `tsconfig.json`:
-```diff
- // nuxt.config.js
- module.exports {
- buildModules: [
- '@nuxtjs/composition-api/module',
-- 'pinia/nuxt',
-+ '@pinia/nuxt',
- ],
- }
+```js
+// nuxt.config.js
+module.exports {
+ buildModules: [
+ '@nuxtjs/composition-api/module',
+ 'pinia/nuxt', // [!code --]
+ '@pinia/nuxt', // [!code ++]
+ ],
+}
```
-```diff
- // tsconfig.json
- {
- "types": [
- // ...
-- "pinia/nuxt/types"
-+ "@pinia/nuxt"
- ]
- }
+```json
+// tsconfig.json
+{
+ "types": [
+ // ...
+ "pinia/nuxt/types" // [!code --]
+ "@pinia/nuxt" // [!code ++]
+ ]
+}
```
[Nuxt 专属章节](../ssr/nuxt.md)也值得一读。
})
```
-类似 [getter](./getters.md),action 也可通过 `this` 访问**整个 store 实例**,并支持**完整的类型标注(以及自动补全✨)**。**不同的是,`action` 可以是异步的**,你可以在它们里面 `await` 调用任何 API,以及其他 action!下面是一个使用 [Mande](https://github.com/posva/mande) 的例子。请注意,你使用什么库并不重要,只要你得到的是一个`Promise`,你甚至可以(在浏览器中)使用原生 `fetch` 函数:
+类似 [getter](./getters.md),action 也可通过 `this` 访问**整个 store 实例**,并支持**完整的类型标注(以及自动补全✨)**。**不同的是,`action` 可以是异步的**,你可以在它们里面 `await` 调用任何 API,以及其他 action!下面是一个使用 [Mande](https://github.com/posva/mande) 的例子。请注意,你使用什么库并不重要,只要你得到的是一个`Promise`,你甚至可以 (在浏览器中) 使用原生 `fetch` 函数:
```js
import { mande } from 'mande'
你也完全可以自由地设置任何你想要的参数以及返回任何结果。当调用 action 时,一切类型也都是可以被自动推断出来的。
-Action 可以像方法一样被调用:
-
-```js
-export default defineComponent({
- setup() {
- const main = useMainStore()
- // 作为 store 的一个方法调用该 action
- main.randomizeCounter()
-
- return {}
- },
-})
+Action 可以像函数或者通常意义上的方法一样被调用:
+
+```vue
+<script setup>
+const store = useCounterStore()
+// call the action as a method of the store
+store.randomizeCounter()
+</script>
+<template>
+ <!-- Even on the template -->
+ <button @click="store.randomizeCounter()">Randomize</button>
+</template>
```
## 访问其他 store 的 action %{#accessing-other-stores-actions}%
})
```
-## 使用 `setup()` 时的用法 %{#usage-with-setup}%
-
-你可以将任何 action 作为 store 的一个方法直接调用:
-
-```js
-export default {
- setup() {
- const store = useStore()
-
- store.randomizeCounter()
- },
-}
-```
-
## 使用选项式 API 的用法 %{#usage-with-the-options-api}%
<VueSchoolLink
虽然并不是每个开发者都会使用组合式 API,但 `setup()` 钩子依旧可以使 Pinia 在选项式 API 中更易用。并且不需要额外的映射辅助函数!
-```js
+```vue
+<script>
import { useCounterStore } from '../stores/counter'
-
-export default {
+export default defineComponent({
setup() {
const counterStore = useCounterStore()
-
return { counterStore }
},
methods: {
console.log('New Count:', this.counterStore.count)
},
},
-}
+})
+</script>
```
### 不使用 `setup()` %{#without-setup}%
默认情况下,*action 订阅器*会被绑定到添加它们的组件上(如果 store 在组件的 `setup()` 内)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 `true` 作为第二个参数传递给 *action 订阅器*,以便将其从当前组件中分离:
-```js
-export default {
- setup() {
- const someStore = useSomeStore()
-
- // 在组件被卸载后,这个订阅依旧会被保留。
- someStore.$onAction(callback, true)
-
- // ...
- },
-}
+```vue
+<script setup>
+const someStore = useSomeStore()
+// this subscription will be kept even after the component is unmounted
+someStore.$onAction(callback, true)
+</script>
```
然后你可以直接访问 store 实例上的 getter 了:
```vue
+<script setup>
+import { useCounterStore } from './counterStore'
+const store = useCounterStore()
+</script>
<template>
<p>Double count is {{ store.doubleCount }}</p>
</template>
-
-<script>
-export default {
- setup() {
- const store = useStore()
-
- return { store }
- },
-}
-</script>
```
## 访问其他 getter %{#accessing-other-getters}%
并在组件中使用:
```vue
-<script>
-export default {
- setup() {
- const store = useStore()
-
- return { getUserById: store.getUserById }
- },
-}
+<script setup>
+import { useUserListStore } from './store'
+const userList = useUserListStore()
+const { getUserById } = storeToRefs(userList)
+// note you will have to use `getUserById.value` to access
+// the function within the <script setup>
</script>
<template>
作为 store 的一个属性,你可以直接访问任何 getter(与 state 属性完全一样):
-```js
-export default {
- setup() {
- const store = useStore()
-
- store.count = 3
- store.doubleCount // 6
- },
-}
+```vue
+<script setup>
+const store = useCounterStore()
+store.count = 3
+store.doubleCount // 6
+</script>
```
## 使用选项式 API 的用法 %{#usage-with-the-options-api}%
虽然并不是每个开发者都会使用组合式 API,但 `setup()` 钩子依旧可以使 Pinia 在选项式 API 中更易用。并且不需要额外的映射辅助函数!
-```js
+```vue
+<script>
import { useCounterStore } from '../stores/counter'
-export default {
+export default defineComponent({
setup() {
const counterStore = useCounterStore()
return this.counterStore.doubleCount * 2
},
},
-}
+})
+</script>
```
+这在将组件从选项式 API 迁移到组合式 API 时很有用,但**应该只是一个迁移步骤**,始终尽量不要在同一组件中混合两种 API 样式。
+
### 不使用 `setup()` %{#without-setup}%
你可以使用[前一节的 state](./state.md#options-api) 中的 `mapState()` 函数来将其映射为 getters:
// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
-export const useStore = defineStore('main', {
+export const useAlertsStore = defineStore('alerts', {
// 其他配置...
})
```
## 使用 Store %{#using-the-store}%
-虽然我们前面定义了一个 store,但在 `setup()` 调用 `useStore()` 之前,store 实例是不会被创建的:
+虽然我们前面定义了一个 store,但在我们使用 `<script setup>` 调用 `useStore()`(或者使用 `setup()` 函数,**像所有的组件那样**) 之前,store 实例是不会被创建的:
-```js
+```vue
+<script setup>
import { useCounterStore } from '@/stores/counter'
-
-export default {
- setup() {
- const store = useCounterStore()
-
- return {
- // 为了能在模板中使用它,你可以返回整个 Store 实例。
- store,
- }
- },
-}
+// access the `store` variable anywhere in the component ✨
+const store = useCounterStore()
+</script>
```
你可以定义任意多的 store,但为了让使用 pinia 的益处最大化(比如允许构建工具自动进行代码分割以及 TypeScript 推断),**你应该在不同的文件中去定义 store**。
请注意,`store` 是一个用 `reactive` 包装的对象,这意味着不需要在 getters 后面写 `.value`,就像 `setup` 中的 `props` 一样,**如果你写了,我们也不能解构它**:
-```js
-export default defineComponent({
- setup() {
- const store = useCounterStore()
- // ❌ 这将无法生效,因为它破坏了响应性
- // 这与从 `props` 中解构是一样的。
- const { name, doubleCount } = store
-
- name // "eduardo"
- doubleCount // 2
-
- return {
- // 始终是 "eduardo"
- name,
- // 始终是 2
- doubleCount,
- // 这个将是响应式的
- doubleValue: computed(() => store.doubleCount),
- }
- },
-})
+```vue
+<script setup>
+const store = useCounterStore()
+// ❌ This won't work because it breaks reactivity
+// it's the same as destructuring from `props`
+const { name, doubleCount } = store // [!code warning]
+name // will always be "Eduardo" // [!code warning]
+doubleCount // will always be 0 // [!code warning]
+setTimeout(() => {
+ store.increment()
+}, 1000)
+// ✅ this one will be reactive
+// 💡 but you could also just use `store.doubleCount` directly
+const doubleValue = computed(() => store.doubleCount)
+</script>
```
为了从 store 中提取属性时保持其响应性,你需要使用 `storeToRefs()`。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:
-```js
+````vue
+<script setup>
import { storeToRefs } from 'pinia'
-
-export default defineComponent({
- setup() {
- const store = useCounterStore()
- // `name` and `doubleCount` 都是响应式 refs
- // 这也将为由插件添加的属性创建 refs
- // 同时会跳过任何 action 或非响应式(非 ref/响应式)属性
- const { name, doubleCount } = storeToRefs(store)
- // 名为 increment 的 action 可以直接提取
- const { increment } = store
-
- return {
- name,
- doubleCount,
- increment,
- }
- },
-})
+const store = useCounterStore()
+// `name` and `doubleCount` are reactive refs
+// This will also extract refs for properties added by plugins
+// but skip any action or non reactive (non ref/reactive) property
+const { name, doubleCount } = storeToRefs(store)
+// the increment action can just be destructured
+const { increment } = store
+</script>
```
-# Plugins %{#plugins}%
+# 插件 %{#plugins}%
由于有了底层 API 的支持,Pinia store 现在完全支持扩展。以下是你可以扩展的内容:
```js
import { createPinia } from 'pinia'
-// å\9c¨å®\89è£\85æ¤æ\8f\92ä»¶å\90\8eå\88\9b建ç\9a\84æ¯\8f个 store ä¸é\83½ä¼\9aæ·»å\8a ä¸\80个å\90\8d为 `secret` ç\9a\84å±\9eæ\80§ã\80\82
-// 插件可以保存在不同的文件中
+// 创建的每个 store 中都会添加一个名为 `secret` 的属性。
+// 在安装此插件后,插件可以保存在不同的文件中
function SecretPiniaPlugin() {
return { secret: 'the cake is a lie' }
}
// 你也可以定义更简单的值
simpleNumber: number
+
+ // type the router added by the plugin above (#adding-new-external-properties)
+ router: Router
}
}
```
默认情况下,*state subscription* 会被绑定到添加它们的组件上(如果 store 在组件的 `setup()` 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 `{ detached: true }` 作为第二个参数,以将 *state subscription* 从当前组件中*分离*:
-```js
-export default {
- setup() {
- const someStore = useSomeStore()
-
- // 在组件被卸载后,该订阅依旧会被保留。
- someStore.$subscribe(callback, { detached: true })
-
- // ...
- },
-}
+```vue
+<script setup>
+const someStore = useSomeStore()
+// this subscription will be kept even after the component is unmounted
+someStore.$subscribe(callback, { detached: true })
+</script>
```
:::tip
-你可以在 `pinia` 实例上侦听整个 state。
+ä½ å\8f¯ä»¥å\9c¨ `pinia` å®\9eä¾\8bä¸\8a使ç\94¨ `watch()` å\87½æ\95°ä¾¦å\90¬æ\95´ä¸ª stateã\80\82
```js
watch(
这样才能提供 devtools 的支持。在 Vue 3 中,一些功能仍然不被支持,如 time traveling 和编辑,这是因为 vue-devtools 还没有相关的 API,但 devtools 也有很多针对 Vue 3 的专属功能,而且就开发者的体验来说,Vue 3 整体上要好得多。在 Vue 2 中,Pinia 使用的是 Vuex 的现有接口(因此不能与 Vuex 一起使用)。
-## Store 是什么?{#what-is-a-store}
+## Store 是什么?%{#what-is-a-store}%
Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,**它承载着全局状态**。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有**三个概念**,[state](./core-concepts/state.md)、[getter](./core-concepts/getters.md) 和 [action](./core-concepts/actions.md),我们可以假设这些概念相当于组件中的 `data`、 `computed` 和 `methods`。
Pinia [起始](https://github.com/vuejs/pinia/commit/06aeef54e2cad66696063c62829dac74e15fd19e)于 2019 年 11 月左右的一次实验,其目的是设计一个拥有[组合式 API](https://github.com/vuejs/composition-api) 的 Vue 状态管理库。从那时起,我们就倾向于同时支持 Vue 2 和 Vue 3,并且不强制要求开发者使用组合式 API,我们的初心至今没有改变。除了**安装**和 **SSR** 两章之外,其余章节中提到的 API 均支持 Vue 2 和 Vue 3。虽然本文档主要是面向 Vue 3 的用户,但在必要时会标注出 Vue 2 的内容,因此 Vue 2 和 Vue 3 的用户都可以阅读本文档。
-## 为什么你应该使用 Pinia?{#why-should-i-use-pinia}
+## 为什么你应该使用 Pinia?%{#why-should-i-use-pinia}%
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的 `export const state = reactive({})` 来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:
然后你就可以在一个组件中使用该 store 了:
-```js
+```vue
+<script setup>
import { useCounterStore } from '@/stores/counter'
-
-export default {
- setup() {
- const counter = useCounterStore()
-
- counter.count++
- // 带有自动补全 ✨
- counter.$patch({ count: counter.count + 1 })
- // 或者使用 action 代替
- counter.increment()
- },
-}
+const counter = useCounterStore()
+counter.count++
+// with autocompletion ✨
+counter.$patch({ count: counter.count + 1 })
+// or using an action instead
+counter.increment()
+</script>
+<template>
+ <!-- Access the state directly from the store -->
+ <div>Current Count: {{ counter.count }}</div>
+</template>
```
-为实现更多高级用法,你甚至可以使用一个函数(与组件 `setup()` 类似)来定义一个 Store:
+为实现更多高级用法,你甚至可以使用一个函数 (与组件 `setup()` 类似) 来定义一个 Store:
```js
export const useCounterStore = defineStore('counter', () => {
// ...
})
-export default {
+export default defineComponent({
computed: {
// 其他计算属性
// ...
// 允许读取 this.increment()
...mapActions(useCounterStore, ['increment']),
},
-}
+})
```
你将会在核心概念部分了解到更多关于每个**映射辅助函数**的信息。
-## 为什么取名 *Pinia*?{#why-pinia}
+## 为什么取名 *Pinia*?%{#why-pinia}%
Pinia (发音为 `/piːnjʌ/`,类似英文中的 “peenya”) 是最接近有效包名 piña (西班牙语中的 *pineapple*,即“菠萝”) 的词。 菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。 与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。 它(菠萝)也是一种原产于南美洲的美味热带水果。
只要你只在 `setup` 函数、`getter` 和 `action` 的顶部调用你定义的 `useStore()` 函数,那么使用 Pinia 创建 store 对于 SSR 来说应该是开箱即用的:
-```js
-export default defineComponent({
- setup() {
- // 这样做的原因是 Pinia 知道
- // `setup()` 中运行的应用是什么
- const main = useMainStore()
- return { main }
- },
-})
+```vue
+<script setup>
+// this works because pinia knows what application is running inside of
+// `setup`
+const main = useMainStore()
+</script>
```
## 在 `setup()` 外部使用 store %{#using-the-store-outside-of-setup}%
## 安装 %{#installation}%
```bash
-yarn add @pinia/nuxt
+yarn add pinia @pinia/nuxt
# 或者使用 npm
-npm install @pinia/nuxt
+npm install pinia @pinia/nuxt
```
+:::tip
+如果你正在使用 npm,你可能会遇到 *ERESOLVE unable to resolve dependency tree* 错误。如果那样的话,将以下内容添加到 `package.json` 中:
+
+```js
+"overrides": {
+ "vue": "latest"
+}
+```
+:::
+
我们提供了一个 *module* 来为你处理一切,你只需要在 `nuxt.config.js` 文件的 `modules` 中添加它。
```js
}
```
+与 `onServerPrefetch()` 一样,如果你想在 `asyncData()` 中调用一个存储动作,你不需要做任何特别的事情。
+
+```vue
+<script setup>
+const store = useStore()
+const { data } = await useAsyncData('user', () => store.fetchUser())
+</script>
+```
+
## 自动引入 %{#auto-imports}%
默认情况下,`@pinia/nuxt` 会暴露一个自动引入的方法:`usePinia()`,它类似于 `getActivePinia()`,但在 Nuxt 中效果更好。你可以添加自动引入来减轻你的开发工作: