Pinia中文文档(拓展)(详细翻译官方文档)

2,482 阅读14分钟

Pinia 中文文档预览地址:baimingxuan.github.io/pinia-doc-c…

服务端渲染 (SSR)

**

TIP

如果您使用的是Nuxt.js,您需要阅读这些说明

**

使用Pinia创建stores对于SSR来说应该是开箱即用的,只要您在setup函数,gettersactions的顶部调用您的useStore()函数:

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

不在 setup() 中使用 store

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

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

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

router.beforeEach((to) => {
  // ✅ This will work make sure the correct store is used for the
  // current running app
  const main = useMainStore(pinia)

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

Pinia方便地将自己作为$Pinia添加到你的应用程序中,因此您可以在像serverPrefetch()这样的函数中使用它:

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

状态激活

为了激活初始状态,您需要确保在HTML的某个地方包含了rootState,以便Pinia以后可以获取它。根据您用于SSR的内容,出于安全原因,您应该转义该状态。我们建议使用Nuxt.js@nuxt/devalue 插件:

import devalue from '@nuxt/devalue'
import { createPinia } from 'pinia'
// retrieve the rootState server side
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)

// after rendering the page, the root state is build and can be read directly
// on `pinia.state.value`.

// serialize, escape (VERY important if the content of the state can be changed
// by the user, which is almost always the case), and place it somewhere on
// the page, for example, as a global variable.
devalue(pinia.state.value)

根据您SSR使用的内容,您将设置一个将在HTML序列化的初始状态变量。您还应该保护自己免受XSS攻击。例如,使用[

vite-ssr

](github.com/frandiox/vi…

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) {
      // this will be stringified and set to window.__INITIAL_STATE__
      initialState.pinia = pinia.state.value
    } else {
      // on the client side, we restore the state
      pinia.state.value = initialState.pinia
    }
  }
)

您可以根据需要,使用其他替代@nuxt/devalue的方法,例如,如果您可以使用JSON.stringify()/JSON.parse()序列化和解析您的状态,则可以大大提高您的性能。

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

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

// must be set by the user
if (isClient) {
  pinia.state.value = JSON.parse(window.__pinia)
}

Nuxt.js

PiniaNuxt.js一起使用是更容易的,因为Nuxt在服务器端渲染方面处理了很多事情。例如,您不需要关心序列化或XSS攻击的问题。

安装

请确保pinia@nuxtjs/composition-api一起安装:

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

我们提供了一个模块来为您处理所有事情,您只需在nuxt.config.js文件中将其添加到buildModules模块中:

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

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

不在 setup() 中使用 store

如果您不想在setup()中使用store,请记住将pinia对象传递给useStore()。我们将它添加到上下文中,这样你就可以在asyncData()fetch()中访问它:

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

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

在 stores 中使用 Nuxt 上下文

通过使用注入的$nuxt属性,你也可以在任何store中使用上下文

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

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

将 Pinia 与 Vuex 一起使用

建议避免同时使用PiniaVuex,但如果您需要同时使用,您需要告诉Pinia不要禁用它:

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

TypeScript

如果您使用的是TypeScriptjsconfig.json,您还应该添加context.pinia的类型:

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

这也将确保您具有自动补全功能😉。

从 Vuex≤4 迁移

尽管VuexPiniastores结构不同,但许多逻辑可以重用。本指南旨在帮助您完成整个过程,并指出可能出现的一些常见问题。

准备

首先,按照[

入门指南

](baimingxuan.net/pinia-doc-c… Pinia

将模块重组为Store

Vuex 有个单一store包含多模块的的概念。这些模块可以选择使用命名空间,甚至可以相互嵌套。

将这个概念转变为使用Pinia,最简单方法是,您以前是使用不同的模块,现在是使用单一的 store。每个store都必需一个id,类似于Vuex中的命名空间。这意味着每个store都是按设计命名的。嵌套模块也可以各自成为自己的store。相互依赖的store将被简便地导入到其他store

如何选择将Vuex模块重组到Piniastore完全取决于您,但这里还有一些建议:

# Vuex example (assuming namespaced modules)
src
└── store
    ├── index.js           # Initializes Vuex, imports modules
    └── modules
        ├── module1.js     # 'module1' namespace
        └── nested
            ├── index.js   # 'nested' namespace, imports module2 & module3
            ├── module2.js # 'nested/module2' namespace
            └── module3.js # 'nested/module3' namespace

# Pinia equivalent, note ids match previous namespaces
src
└── stores
    ├── index.js          # (Optional) Initializes Pinia, does not import stores
    ├── module1.js        # 'module1' id
    ├── nested-module2.js # 'nested/module3' id
    ├── nested-module3.js # 'nested/module2' id
    └── nested.js         # 'nested' id

这为stores创建了一个扁平的结构,但也保留了和之前使用id等价的命名空间。如果你在store的根目录(在Vuexstore/index.js文件中)中有一些state/getters/actions/mutations,你可能希望创建另一个名为rootstore,并且它包含所有这些信息。

Pinia的目录通常称为stores而不是store。这是为了强调Pinia使用了多个store,而不是Vuex中的单一store

对于大型项目,您可能希望逐个模块进行转换,而不是一次性转换所有的内容。实际上,您可以在迁移过程中混合使用PiniaVuex,这种方式也是可行的,这也是命名Pinia目录为stores的另一个原因。

单个模块的转换

这是个将Vuex模块转换为Pinia``store前后的完整示例,请参阅下面的分步指南。Pinia示例使用选项store,因为它的结构与Vuex最相似:

// Vuex module in the 'auth/user' namespace
import { Module } from 'vuex'
import { api } from '@/api'
import { RootState } from '@/types' // if using a Vuex type definition

interface State {
  firstName: string
  lastName: string
  userId: number | null
}

const storeModule: Module<State, RootState> = {
  namespaced: true,
  state: {
    firstName: '',
    lastName: '',
    userId: null
  },
  getters: {
    firstName: (state) => state.firstName,
    fullName: (state) => `${state.firstName} ${state.lastName}`,
    loggedIn: (state) => state.userId !== null,
    // combine with some state from other modules
    fullUserDetails: (state, getters, rootState, rootGetters) => {
      return {
        ...state,
        fullName: getters.fullName,
        // read the state from another module named `auth`
        ...rootState.auth.preferences,
        // read a getter from a namespaced module called `email` nested under `auth`
        ...rootGetters['auth/email'].details
      }
    }
  },
  actions: {
    async loadUser ({ state, commit }, id: number) {
      if (state.userId !== null) throw new Error('Already logged in')
      const res = await api.user.load(id)
      commit('updateUser', res)
    }
  },
  mutations: {
    updateUser (state, payload) {
      state.firstName = payload.firstName
      state.lastName = payload.lastName
      state.userId = payload.userId
    },
    clearUser (state) {
      state.firstName = ''
      state.lastName = ''
      state.userId = null
    }
  }
}

export default storeModule

// Pinia Store
import { defineStore } from 'pinia'
import { useAuthPreferencesStore } from './auth-preferences'
import { useAuthEmailStore } from './auth-email'
import vuexStore from '@/store' // for gradual conversion, see fullUserDetails

interface State {
  firstName: string
  lastName: string
  userId: number | null
}

export const useAuthUserStore = defineStore('auth/user', {
  // convert to a function
  state: (): State => ({
    firstName: '',
    lastName: '',
    userId: null
  }),
  getters: {
    // firstName getter removed, no longer needed
    fullName: (state) => `${state.firstName} ${state.lastName}`,
    loggedIn: (state) => state.userId !== null,
    // must define return type because of using `this`
    fullUserDetails (state): FullUserDetails {
      // import from other stores
      const authPreferencesStore = useAuthPreferencesStore()
      const authEmailStore = useAuthEmailStore()
      return {
        ...state,
        // other getters now on `this`
        fullName: this.fullName,
        ...authPreferencesStore.$state,
        ...authEmailStore.details
      }

      // alternative if other modules are still in Vuex
      // return {
      //   ...state,
      //   fullName: this.fullName,
      //   ...vuexStore.state.auth.preferences,
      //   ...vuexStore.getters['auth/email'].details
      // }
    }
  },
  actions: {
    // no context as first argument, use `this` instead
    async loadUser (id: number) {
      if (this.userId !== null) throw new Error('Already logged in')
      const res = await api.user.load(id)
      this.updateUser(res)
    },
    // mutations can now become actions, instead of `state` as first argument use `this`
    updateUser (payload) {
      this.firstName = payload.firstName
      this.lastName = payload.lastName
      this.userId = payload.userId
    },
    // easily reset state using `$reset`
    clearUser () {
      this.$reset()
    }
  }
})

让我们将以上内容分成几个步骤:

  1. store添加一个必需的id,您可能希望保持与之前的名称空间相同

  2. 如果state还不是一个函数,则需将它转换成函数

  3. 转换getters

  4. 删除任何以相同名称返回状态的getters(如firstName:(state) => state.firstName)这些不是必需的,因为您可以直接从store实例访问任何状态

  5. 如果您需要访问其它getters,可以使用this代替,而不是使用第二个参数。请记住,如果您正在使用this,那么您将不得不使用常规函数而不是箭头函数。另外请注意,由于TS的限制,您需要返回指定的类型,请参阅此处了解更多详细信息

  6. 如果使用rootStaterootGetters参数,则通过直接导入其他store来替换它们,或者如果它们仍然存在于Vuex中,则直接从Vuex访问它们

  7. 转换actions

  8. 从所有action中删除第一个context参数,所有东西都应该通过this访问

  9. 如果使用其它的stores,要么直接导入它们,要么在Vuex上访问它们,这与getters一样

  10. 转换mutations

  11. mutations不再存在。这些可以转换为actions,或者您可以直接分配给组件中的store(例如:userStore.firstName = 'First')

  12. 如果转换为actions,则需删除第一个参数state,并用this代替所有工作

  13. 一个常见的mutation是将状态重置回初始状态。这是store$reset方法的内置功能。请注意,此功能仅适用于option stores

如您所见,您的大部分代码都可以重用。如果遗漏了什么,类型安全还会帮助您确定需要更改什么。

组件内使用

现在您的Vuex模块已经转换为Pinia store,使用该模块的任何组件或其他文件也需要更新。

如果您之前使用过 Vuex 的辅助函数,那么值得看非setup()的指南,因为这些辅助函数大多可以重用。

如果您正在使用useStore,则直接导入新store并访问其上的状态。例如:

// Vuex
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'

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

    const firstName = computed(() => store.state.auth.user.firstName)
    const fullName = computed(() => store.getters['auth/user/firstName'])

    return {
      firstName,
      fullName
    }
  }
})

// Pinia
import { defineComponent, computed } from 'vue'
import { useAuthUserStore } from '@/stores/auth-user'

export default defineComponent({
  setup () {
    const authUserStore = useAuthUserStore()

    const firstName = computed(() => authUserStore.firstName)
    const fullName = computed(() => authUserStore.fullName)

    return {
      // you can also access the whole store in your component by returning it
      authUserStore,
      firstName,
      fullName
    }
  }
})

组件外使用

只要注意不在函数外部使用store,更新组件之外的用法都是很简单的。这是在Vue Router导航守卫中使用store的示例:

// Vuex
import vuexStore from '@/store'

router.beforeEach((to, from, next) => {
  if (vuexStore.getters['auth/user/loggedIn']) next()
  else next('/login')
})

/ Pinia
import { useAuthUserStore } from '@/stores/auth-user'

router.beforeEach((to, from, next) => {
  // Must be used within the function!
  const authUserStore = useAuthUserStore()
  if (authUserStore.loggedIn) next()
  else next('/login')
})

想了解更多细节可以点击 [

这里

](baimingxuan.net/pinia-doc-c…) 。

Vuex 进阶使用

如果您Vuexstore使用了它提供的一些更高级的功能,下面是一些关于如何在Pinia 完成相同功能的指南。其中一些要点已收录在

比较摘要

中。

动态模块

无需在Pinia中动态注册模块。stores在设计上是动态的,仅在需要时才注册。如果store从没被使用,则永远不会“注册”。

热模块更新

HMR也受支持,但需要替换,请参阅

HMR指南

插件

如果您使用开源的Vuex插件,那么检查是否有Pinia的替代品。如果没有,您将需要自己编写或评估该插件是否仍然必要。

如果您已经编写了自己的插件,那么很可能需要对其更新,以便能与Pinia一起使用。请参阅

插件指南

HMR (热模块更新)

Pinia支持热模块更新,因此您可以直接在您的应用程序中修改您的stores并与它们进行交互,而无需重新加载页面,从而允许您保留现有的状态,添加,甚至删除stateactions,和getters

目前,官方只支持Vite,但任何执行import.meta.hot规范的打包器都应该能生效(webpack似乎使用import.meta.webpackHot而不是import.meta.hot)。您需要在任何store声明旁边添加这段代码。假设您有三个storesauth.js, cart.js, 和chat.js, 您必须在创建store定义后添加(并调整)它:

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

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

// make sure to pass the right store definition, `useAuth` in this case.
if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot))
}

测试 stores

从设计上讲,stores将在许多地方被使用,并且会使测试变得比应该的困难得多。幸运的是,事实并非如此。在测试stores时,我们需要注意以下三点:

  • Pinia实例:没有它,stores就无法运作

  • Actions:大多数时候,它们包含了stores最复杂的逻辑。如果他们默认被模拟不是很好吗?

  • Plugins:如果您依赖于插件,您也必须安装它们并进行测试

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

  • 测试 Stores

  • 单元测试store

  • 单元测试组件

  • E2E测试

  • 单元测试组件(Vue 2)

单元测试 Store

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

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

describe('Counter Store', () => {
  beforeEach(() => {
    // creates a fresh pinia and make it active so it's automatically picked
    // up by any useStore() call without having to pass it to it:
    // `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插件,有一件重要的事情要知道:在应用安装pinia之前,插件不会被使用。可以通过创建一个空应用或假应用来解决:

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

// same code as above...

// you don't need to create one app per test
const app = createApp({})
beforeEach(() => {
  const pinia = createPinia().use(somePlugin)
  app.use(pinia)
  setActivePinia(pinia)
})

单元测试组件

可以通过 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() // uses the testing pinia!

// state can be directly manipulated
store.name = 'my new name'
// can also be done through patch
store.$patch({ name: 'new name' })
expect(store.name).toBe('new name')

// actions are stubbed by default but can be configured by
// passing an option to `createTestingPinia()`
store.someAction()

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

请注意,如果您使用的是Vue 2@vue/test-utils会有些许不同的配置。

您可以在测试包的测试里找到更多示例。

E2E 测试

对于Pinia,您无需为

E2E

测试更改任何东西,这是E2E测试最重要的点!您也可以测试HTTP请求,但这已经超出了本指南的范围😄。

单元测试组件(Vue 2)

当使用[Vue Test Utils 1](https://v1.test-utils.vuejs.org/)时,在localVue上安装Pinia:

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

const localVue = createLocalVue()
localVue.use(PiniaVuePlugin)

const wrapper = mount(Counter, {
  localVue,
  pinia: createTestingPinia(),
})

const store = useSomeStore() // uses the testing pinia!

不使用 setup() 的用法

即使不使用Composition API,也可以使用Pinia(如果您使用的是Vue 2,就需要安装@vue/composition-api插件)。虽然我们建议您尝试使用Composition API或者学习它,但现在对于您和您的团队可能还不是时候,您可能正在迁移应用程序的过程中,或者出于其他原因。这儿有几个可能帮到你函数:

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

获取整个 store 的访问权限

如果你需要访问store中几乎所有的内容,那么对于store的每个属性都需要做映射......相反,你可以通过mapStores()访问整个store

import { mapStores } from 'pinia'

// given two stores with the following ids
const useUserStore = defineStore('user', {
  // ...
})
const useCartStore = defineStore('cart', {
  // ...
})

export default {
  computed: {
    // note we are not passing an array, just one store after the other
    // each store will be accessible as its id + 'Store'
    ...mapStores(useCartStore, useUserStore),
    }),
  },

  methods: {
    async buyStuff() {
      // use them anywhere!
      if (this.userStore.isAuthenticated()) {
        await this.cartStore.buy()
        this.$router.push('/purchased')
      }
    },
  },
}

默认情况下,Pania将为所有storeid添加"Store"后缀。您也可以通过调用setMapStoreSuffix()来自定义:

import { createPinia, setMapStoreSuffix } from 'pinia'

// completely remove the suffix: this.user, this.cart
setMapStoreSuffix('')
// this.user_store, this.cart_store (it's okay, I won't judge you)
setMapStoreSuffix('_store')
export const pinia = createPinia()

TypeScript

默认情况下,所有的辅助函数都提供自动补全的功能,因此你不需要做任何事情。

如果您调用setMapStoreSuffix()更改"Store"后缀,您还需要将其添加到TS文件或global.d.ts文件中的某个地方。最方便的地方是您调用setMapStoreSuffix()的地方:

import { createPinia, setMapStoreSuffix } from 'pinia'

setMapStoreSuffix('') // completely remove the suffix
export const pinia = createPinia()

declare module 'pinia' {
  export interface MapStoresCustomization {
    // set it to the same value as above
    suffix: ''
  }
}

WARNING
如果您使用TypeScript声明文件(如global.d.ts),请确保在它的头部引入'pinia'来公开所有现有的类型。

组合 Stores 

组合stores是为了让store间相互使用,并要遵循下面一些规则:

如果两个或多个store相互使用,它们不能通过gettersactions创建无限循环。也不能在它们设置的函数中直接读取彼此的状态:

const useX = defineStore('x', () => {
  const y = useY()

  // ❌ This is not possible because y also tries to read x.name
  y.name

  function doSomething() {
    // ✅ Read y properties in computed or actions
    const yName = y.name
    // ...
  }

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

const useY = defineStore('y', () => {
  const x = useX()

  // ❌ This is not possible because x also tries to read y.name
  x.name

  function doSomething() {
    // ✅ Read x properties in computed or actions
    const xName = x.name
    // ...
  }

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

嵌套 Stores

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

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

import { useUserStore } from './user'

export const cartStore = defineStore('cart', {
  getters: {
    // ... other 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)
    },
  },
})

共享 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}.`
    },
  },
})

共享 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)
      }
    },
  },
})

从 0.x (v1) 迁移到 v2

从 2.0.0-rc.4 支持版本开始,piniaVue 2Vue 3都支持!这意味着,所有最新的更新都将应用于V2版本,因此Vue 2Vue 3用户都能从中受益。如果您使用的是Vue 3,这不会为您带来任何改变,因为您已经在使用rc,您可以查看CHANGELOG以获取所有更改的详细说明。否则,这篇指南就是为您准备的!

弃用的部分

让我们看看需要应用到代码中的所有变更。首先,请确保您已经在运行最新的0.x版本以查看弃用的内容:

npm i 'pinia@^0.x.x'
# or with yarn
yarn add 'pinia@^0.x.x'

如果您正在使用ESLint,请考虑使用此插件来找到所有已弃用的内容。否则,您应该相互对比查看它们之间相同的内容。下面是那些已弃用并被移除的API

  • createStore()变成defineStore()

  • 订阅中的storeName变成storeId

  • PiniaPlugin被重命名为PiniaVuePlugin(用于Vue 2Pinia插件)

  • $subscribe()不再接受布尔值作为第二个参数,而是通过传递一个对象detached: true代替。

  • Pinia插件不再直接接收storeid。使用store.$id代替。

重大的变更

删除这些后,您可以使用以下命令升级到v2版本:

npm i 'pinia@^2.x.x'
# or with yarn
yarn add 'pinia@^2.x.x'

并开始更新您的代码。

Store 泛型

2.0.0-rc.0中新增

所有该类型的用法已由StoreGeneric替换为GenericStore。这是新的store泛型,它能够接受任何类型的store。如果您使用store类型编写函数而没传递其泛型(如 Store<Id, State, Getters, Actions>),您应该使用StoreGeneric作为没有泛型Store的类型,并创建一个空的store类型。

-function takeAnyStore(store: Store) {}
+function takeAnyStore(store: StoreGeneric) {}

-function takeAnyStore(store: GenericStore) {}
+function takeAnyStore(store: StoreGeneric) {}

专为插件的 DefineStoreOptions

如果您正在使用TypeScript编写插件,并扩展DefineStoreOptions类型以添加自定义选项,您应将其重命名为DefineStoreOptionsBase。此类型在setupoptions stores都适用。

declare module 'pinia' {
-  export interface DefineStoreOptions<S, Store> {
+  export interface DefineStoreOptionsBase<S, Store> {
     debounce?: {
       [k in keyof StoreActions<Store>]?: number
     }
   }
 }

PiniaStorePlugin 被重命名

类型PiniaStorePlugin已重命名为PiniaPlugin

-import { PiniaStorePlugin } from 'pinia'
+import { PiniaPlugin } from 'pinia'

-const piniaPlugin: PiniaStorePlugin = () => {
+const piniaPlugin: PiniaPlugin = () => {
   // ...
 }

请注意,此变更能生效的前提是,将Pinia升级到最新版本。

@vue/composition-api 版本

由于pinia依赖于effectScope(),因此您使用@vue/composition-api的版本至少为 1.1.0

npm i @vue/composition-api@latest
# or with yarn
yarn add @vue/composition-api@latest

webpack 4 支持

如果您使用的是webpack 4Vue CLI使用webpack 4),您可能会遇到如下错误:

ERROR  Failed to compile with 18 errors

 error  in ./node_modules/pinia/dist/pinia.mjs

Can't import the named export 'computed' from non EcmaScript module (only default export is available)

这是由于dist文件的现代化以支持Node.js中的原生ESM模块。文件使用.mjs.cjs扩展名来让Node从中受益。要解决此问题,您有两种可能:

  • 如果您使用的是Vue CLI 4.x,请升级您的依赖项。这应该包括下面的修复。

  • 如果您无法升级,请将其添加到您的vue.config.js

    // vue.config.js module.exports = { configureWebpack: { module: { rules: [ { test: /.mjs$/, include: /node_modules/, type: 'javascript/auto', }, ], }, }, }

  • 如果您手动处理webpack,您必须让它知道如何处理.mjs文件:

    // webpack.config.js module.exports = { module: { rules: [ { test: /.mjs$/, include: /node_modules/, type: 'javascript/auto', }, ], }, }

开发工具

Pinia v2不再支持Vue Devtools v5,它需要Vue Devtools v6。[

在Vue Devtools 文档

](devtools.vuejs.org/guide/insta…

Nuxt

如果您使用Nuxtpinia现在有它的专有Nuxt包🎉。安装它:

npm i @pinia/nuxt
# or with yarn
yarn add @pinia/nuxt

还要确保更新您的**@nuxtjs/composition-api**包。

如果您使用的是TypeScript ,请调整您nuxt.config.jstsconfig.json的配置:

// nuxt.config.js
 module.exports {
   buildModules: [
     '@nuxtjs/composition-api/module',
-    'pinia/nuxt',
+    '@pinia/nuxt',
   ],
 }

 // tsconfig.json
 {
   "types": [
     // ...
-    "pinia/nuxt/types"
+    "@pinia/nuxt"
   ]
 }

还建议您阅读Nuxt专用章节。

文档基础篇地址:Pinia中文文档(基础)(详细翻译官方文档)

Pinia 中文文档预览地址:baimingxuan.github.io/pinia-doc-c…