入门指南
核心概念
服务端渲染
开发者指南
- Migrating from Vuex ≤4
- HMR (Hot Module Replacement)
- 测试存储商店
- Usage without setup()
- Composing Stores
- Migrating from 0.x (v1) to v2
API 手册
- API Documentation
- Module: pinia
- Module: @pinia/nuxt
- Module: @pinia/testing
- Enumeration: MutationType
- Interface: TestingOptions
- Interface: DefineSetupStoreOptions
- Interface: DefineStoreOptions
- Interface: DefineStoreOptionsBase
- Interface: DefineStoreOptionsInPlugin
- Interface: MapStoresCustomization
- Interface: Pinia
- Interface: PiniaCustomProperties
- Interface: PiniaCustomStateProperties
- Interface: PiniaPlugin
- Interface: PiniaPluginContext
- Interface: StoreDefinition
- Interface: StoreProperties
- Interface: SubscriptionCallbackMutationDirect
- Interface: SubscriptionCallbackMutationPatchFunction
- Interface: SubscriptionCallbackMutationPatchObject
- Interface: _StoreOnActionListenerContext
- Interface: _StoreWithState
- Interface: _SubscriptionCallbackMutationBase
- Interface: TestingPinia
Migrating from Vuex ≤4
虽然 Vuex 和 Pinia store 的结构不同,但是很多逻辑是可以复用的。 本指南旨在帮助您完成整个过程并指出可能出现的一些常见问题。
Preparation
首先,按照 入门指南 安装 Pinia。
将模块重组为商店
Vuex 有一个包含多个模块的单一商店的概念。 这些模块可以选择命名空间,甚至可以相互嵌套。
将该概念转换为与 Pinia 一起使用的最简单方法是,您以前使用的每个模块现在都是一个_store_。 每个商店都需要一个类似于 Vuex 中的命名空间的“id”。 这意味着每个商店都是按设计命名的。 嵌套模块也可以各自成为自己的商店。 相互依赖的商店将简单地导入另一个商店。
如何选择将 Vuex 模块重组为 Pinia 商店完全取决于您,但这里有一个建议:
bash# 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 # 'nestedModule2' id
├── nested-module3.js # 'nestedModule3' id
└── nested.js # 'nested' id
这为商店创建了一个平面结构,但也保留了以前的命名空间与等效的 id
s。 如果你在 store 的根目录中有一些 state/getters/actions/mutations(在 Vuex 的 store/index.js
文件中),你可能希望创建另一个名为 root
的 store 来保存所有这些信息。
Pinia 的目录通常称为 stores
而不是 store
。 这是为了强调 Pinia 使用多个 store,而不是 Vuex 中的单个 store。
对于大型项目,您可能希望逐个模块进行转换,而不是一次转换所有内容。 您实际上可以在迁移期间将 Pinia 和 Vuex 混合在一起,因此这种方法也可以工作,这也是将 Pinia 目录命名为“stores”的另一个原因。
转换单个模块
这是将 Vuex 模块转换为 Pinia 商店之前和之后的完整示例,请参阅下面的分步指南。 Pinia 示例使用选项存储,因为其结构与 Vuex 最相似:
ts// 'auth/user' 命名空间中的 Vuex 模块
import { Module } from 'vuex'
import { api } from '@/api'
import { RootState } from '@/types' // 如果使用 Vuex 类型定义
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,
// 从另一个名为“auth”的模块中读取状态
...rootState.auth.preferences,
// 从嵌套在 `auth` 下的名为 `email` 的命名空间模块中读取 getter
...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
ts// Pinia Store
import { defineStore } from 'pinia'
import { useAuthPreferencesStore } from './auth-preferences'
import { useAuthEmailStore } from './auth-email'
import vuexStore from '@/store' // 对于逐渐转换,请参阅 fullUserDetails
interface State {
firstName: string
lastName: string
userId: number | null
}
export const useAuthUserStore = defineStore('authUser', {
// 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,
// 由于使用 `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
}
// 如果其他模块仍在 Vuex 中,则可以选择
// return {
// ...state,
// fullName: this.fullName,
// ...vuexStore.state.auth.preferences,
// ...vuexStore.getters['auth/email'].details
// }
}
},
actions: {
// 没有上下文作为第一个参数,使用 `this` 代替
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现在可以变成actions,而不是 `state` 作为第一个参数使用 `this`
updateUser (payload) {
this.firstName = payload.firstName
this.lastName = payload.lastName
this.userId = payload.userId
},
// 使用 `$reset` 轻松重置状态
clearUser () {
this.$reset()
}
}
})
让我们将上述内容分解为几个步骤:
- 为 store 添加一个必需的
id
,你可能希望和之前的命名空间保持一致。 还建议确保id
在 camelCase 中,因为它更容易与mapStores()
一起使用。 - 将
state
转换为一个函数,如果它还不是一个函数 - 转换
getter
- 移除任何以相同名称返回状态的 getter(例如,
firstName: (state) => state.firstName
),这些不是必需的,因为您可以直接从 store 实例访问任何状态 - 如果您需要访问其他 getter,它们在
this
上,而不是使用第二个参数。 请记住,如果您使用的是this
,那么您将不得不使用常规函数而不是箭头函数。 另请注意,由于 TS 限制,您需要指定返回类型,请参阅 此处 了解更多详细信息 - 如果使用
rootState
或rootGetters
参数,直接导入其他 store 替换它们,或者如果它们仍然存在于 Vuex 中,则直接从 Vuex 访问它们
- 移除任何以相同名称返回状态的 getter(例如,
- 转换
actions
- 从每个动作中删除第一个
context
参数。 一切都应该可以从this
访问 2.如果使用其他store直接导入或者在Vuex上访问,和getter一样
- 从每个动作中删除第一个
- 转换
mutations
- 突变不再存在。 这些可以转换为
actions
,或者您可以直接分配给组件中的商店(例如,userStore.firstName = 'First'
) - 如果转换为动作,请删除第一个
state
参数并将所有分配替换为this
- 一个常见的突变是将状态重置回其初始状态。 这是 store 的
$reset
方法的内置功能。 请注意,此功能仅适用于期权商店。
- 突变不再存在。 这些可以转换为
如您所见,您的大部分代码都可以重用。 如果遗漏任何内容,类型安全还应该帮助您确定需要更改的内容。
组件内部使用
现在您的 Vuex 模块已经转换为 Pinia 存储,任何使用该模块的组件或其他文件也需要更新。
如果您之前使用过 Vuex 的 map
助手,那么值得查看 Usage without setup() 指南,因为这些助手中的大多数都可以重用。
如果您使用的是 useStore
,则直接导入新商店并访问其上的状态。 例如:
// 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/fullName'])
return {
firstName,
fullName
}
}
})
ts// 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
}
}
})
使用外部组件
只要您小心 不使用函数之外的商店,更新组件之外的用法应该很简单。 这是在 Vue Router 导航守卫中使用 store 的示例:
ts// Vuex
import vuexStore from '@/store'
router.beforeEach((to, from, next) => {
if (vuexStore.getters['auth/user/loggedIn']) next()
else next('/login')
})
ts// 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')
})
可以在 此处 找到更多详细信息。
高级 Vuex 用法
如果您的 Vuex 商店使用它提供的一些更高级的功能,这里有一些关于如何在 Pinia 中完成相同功能的指南。 此比较摘要 中已经涵盖了其中一些要点。
动态模块
无需在 Pinia 中动态注册模块。 商店在设计上是动态的,仅在需要时才注册。 如果从不使用商店,则永远不会“注册”。
热模块更换
HMR 也受支持,但需要更换,请参阅 HMR 指南。
插件
如果您使用公共 Vuex 插件,请检查是否有 Pinia 替代品。 如果没有,您将需要自己编写或评估该插件是否仍然是必要的。
如果您已经编写了自己的插件,那么它可能会被更新以与 Pinia 一起使用。 请参阅 插件指南。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论