Pinia 中文文档预览地址:baimingxuan.github.io/pinia-doc-c…
服务端渲染 (SSR)
**
TIP
如果您使用的是
Nuxt.js
,您需要阅读这些说明。**
使用Pinia
创建stores
对于SSR
来说应该是开箱即用的,只要您在setup
函数,getters
和actions
的顶部调用您的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
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
Pinia
与Nuxt.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 一起使用
建议避免同时使用Pinia
和Vuex
,但如果您需要同时使用,您需要告诉Pinia
不要禁用它:
// nuxt.config.js
export default {
buildModules: [
'@nuxtjs/composition-api/module',
['@pinia/nuxt', { disableVuex: false }],
],
// ... other options
}
TypeScript
如果您使用的是TypeScript
或jsconfig.json
,您还应该添加context.pinia
的类型:
{
"types": [
// ...
"@pinia/nuxt"
]
}
这也将确保您具有自动补全功能😉。
从 Vuex≤4 迁移
尽管Vuex
和Pinia
的stores
结构不同,但许多逻辑可以重用。本指南旨在帮助您完成整个过程,并指出可能出现的一些常见问题。
准备
首先,按照[
入门指南
](baimingxuan.net/pinia-doc-c… Pinia
。
将模块重组为Store
Vuex 有个单一store
包含多模块的的概念。这些模块可以选择使用命名空间,甚至可以相互嵌套。
将这个概念转变为使用Pinia
,最简单方法是,您以前是使用不同的模块,现在是使用单一的 store
。每个store
都必需一个id
,类似于Vuex
中的命名空间。这意味着每个store
都是按设计命名的。嵌套模块也可以各自成为自己的store
。相互依赖的store
将被简便地导入到其他store
。
如何选择将Vuex
模块重组到Pinia
的store
完全取决于您,但这里还有一些建议:
# 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
的根目录(在Vuex
的store/index.js
文件中)中有一些state/getters/actions/mutations
,你可能希望创建另一个名为root
的store
,并且它包含所有这些信息。
Pinia
的目录通常称为stores
而不是store
。这是为了强调Pinia
使用了多个store
,而不是Vuex
中的单一store
。
对于大型项目,您可能希望逐个模块进行转换,而不是一次性转换所有的内容。实际上,您可以在迁移过程中混合使用Pinia
和Vuex
,这种方式也是可行的,这也是命名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()
}
}
})
让我们将以上内容分成几个步骤:
-
为
store
添加一个必需的id
,您可能希望保持与之前的名称空间相同 -
如果
state
还不是一个函数,则需将它转换成函数 -
转换
getters
-
删除任何以相同名称返回状态的
getters
(如firstName:(state) => state.firstName
)这些不是必需的,因为您可以直接从store
实例访问任何状态 -
如果您需要访问其它
getters
,可以使用this
代替,而不是使用第二个参数。请记住,如果您正在使用this
,那么您将不得不使用常规函数而不是箭头函数。另外请注意,由于TS
的限制,您需要返回指定的类型,请参阅此处了解更多详细信息 -
如果使用
rootState
或rootGetters
参数,则通过直接导入其他store
来替换它们,或者如果它们仍然存在于Vuex
中,则直接从Vuex
访问它们 -
转换
actions
-
从所有
action
中删除第一个context
参数,所有东西都应该通过this
访问 -
如果使用其它的
stores
,要么直接导入它们,要么在Vuex
上访问它们,这与getters
一样 -
转换
mutations
-
mutations
不再存在。这些可以转换为actions
,或者您可以直接分配给组件中的store
(例如:userStore.firstName = 'First'
) -
如果转换为
actions,
则需删除第一个参数state
,并用this
代替所有工作 -
一个常见的
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 进阶使用
如果您Vuex
的store
使用了它提供的一些更高级的功能,下面是一些关于如何在Pinia
完成相同功能的指南。其中一些要点已收录在
比较摘要
中。
动态模块
无需在Pinia
中动态注册模块。stores
在设计上是动态的,仅在需要时才注册。如果store
从没被使用,则永远不会“注册”。
热模块更新
HMR
也受支持,但需要替换,请参阅
HMR
指南
。
插件
如果您使用开源的Vuex
插件,那么检查是否有Pinia
的替代品。如果没有,您将需要自己编写或评估该插件是否仍然必要。
如果您已经编写了自己的插件,那么很可能需要对其更新,以便能与Pinia
一起使用。请参阅
插件指南。
HMR (热模块更新)
Pinia
支持热模块更新,因此您可以直接在您的应用程序中修改您的stores
并与它们进行交互,而无需重新加载页面,从而允许您保留现有的状态,添加,甚至删除state
,actions
,和getters
。
目前,官方只支持Vite,但任何执行import.meta.hot
规范的打包器都应该能生效(webpack
似乎使用import.meta.webpackHot
而不是import.meta.hot
)。您需要在任何store
声明旁边添加这段代码。假设您有三个stores
:auth.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
将为所有store
的id
添加"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
相互使用,它们不能通过getters
或actions
创建无限循环。也不能在它们设置的函数中直接读取彼此的状态:
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
,您可以直接引入它。把它想象成嵌套。
您可以在任何getter
或action
的顶部调用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 支持版本开始,pinia
对Vue 2
和Vue 3
都支持!这意味着,所有最新的更新都将应用于V2
版本,因此Vue 2
和Vue 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 2
的Pinia
插件) -
$subscribe()
不再接受布尔值作为第二个参数,而是通过传递一个对象detached: true
代替。 -
Pinia
插件不再直接接收store
的id
。使用store.$id
代替。
重大的变更
删除这些后,您可以使用以下命令升级到v2
版本:
npm i 'pinia@^2.x.x'
# or with yarn
yarn add 'pinia@^2.x.x'
并开始更新您的代码。
Store 泛型
所有该类型的用法已由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
。此类型在setup
和options 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 4
(Vue 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
如果您使用Nuxt
,pinia
现在有它的专有Nuxt
包🎉。安装它:
npm i @pinia/nuxt
# or with yarn
yarn add @pinia/nuxt
还要确保更新您的**@nuxtjs/composition-api
**包。
如果您使用的是TypeScript
,请调整您nuxt.config.js
和tsconfig.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…