最近项目中,想在vue2中把状态管理库从vuex转到pinia。
1. 安装
"@vue/composition-api": "^1.7.0",
"pinia": "^2.0.17",
import { PiniaVuePlugin, createPinia } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
router,
store,
render: h => h(App),
pinia
}).$mount('#app')
2. 配置vue-cli识别mjs后缀文件
由于pinia的源码文件后缀是mjs和cjs,webpack默认不识别该后缀的文件,我们项目中是通过import的方式导入的,需要配置vue-cli匹配到mjs文件时,把文件代码识别成ES Module格式导入
configureWebpack: {
module: {
rules: [
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto'
}
]
}
}
第5点举例:vuex的组织方式可能是这样的:嵌套
src
└── store
├── index.js
└── modules
├── module1.js
└── nested
├── index.js
├── module2.js
└── module3.js
重构成pinia后:平级
src
└── stores
├── index.js
├── module1.js
├── nested-module2.js
├── nested-module3.js
└── nested.js
现有store如下:
import { defineStore } from "pinia";
export const storeA = defineStore("storeA", {
state: () => {
return {
msg: "hello pinia",
name: "hmi",
};
},
getters: {},
actions: {
setName(data) {
this.name = data;
},
},
});
<template>
<div>{{ piniaStoreA.msg }}</div>
</template>
<script setup>
import { storeA } from '@/piniaStore/storeA'
let piniaStoreA = storeA()
piniaStoreA.msg = 'hello world'
</script>
import { storeA } from '@/piniaStore/storeA'
let piniaStoreA = storeA()
piniaStoreA.$patch({
msg: 'hello world',
name: '111'
})
console.log(piniaStoreA.name);//111
import { storeA } from '@/piniaStore/storeA'
let piniaStoreA = storeA()
piniaStoreA.setName('111')
import { storeA } from '@/piniaStore/storeA'
let piniaStoreA = storeA()
piniaStoreA.$reset()
import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'
export default {
computed: {
// 允许访问组件内的 this.doubleCounter
// 与从 store.doubleCounter 中读取相同
...mapState(useCounterStore, ['doubleCount'])
// 与上面相同,但将其注册为 this.myOwnName
...mapState(useCounterStore, {
myOwnName: 'doubleCounter',
// 您还可以编写一个访问 store 的函数
double: store => store.doubleCount * 2,
// 它可以正常读取“this”,但无法正常写入...
magicValue(store) {
return store.someGetter + this.counter + this.double
},
}),
},
}
import { mapStores } from 'pinia'
const useUserStore = defineStore('user', {
// ...
})
const useCartStore = defineStore('cart', {
// ...
})
export default {
computed: {
...mapStores(useCartStore, useUserStore),
}),
},
methods: {
async buyStuff() {
//默认情况下(可改),Pania 会为每个商店id 添加"Store"后缀。
//所以需要以 user+StoreId 的命名去调用
if (this.userStore.isAuthenticated()) {
await this.cartStore.buy()
this.$router.push('/purchased')
}
},
},
}
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counterStore'
export default {
methods: {
// this.increment()的方式去访问
// store.increment() 调用也可以
...mapActions(useCounterStore, ['increment'])
// 给store中的doubleCounter方法在组件中改个名字去调用
...mapActions(useCounterStore, { myOwnName: 'doubleCounter' }),
},
}
1. pinia需要在用到的时候再用
错误用法:
导入立马使用,会报错没有注册pinia,至少给个延迟打印才行
<script>
import storeId from './pinia1.js'
console.log(storeId()) //报错
</script>
正确用法:
data函数不是马上调用的
<script>
import storeId from './pinia1.js'
export default {
data () {
return {
storeId: storeId()
}
},
}
</script>
2. pinia的store不能被解构
错误用法:
count2没有响应性
<template>
{{count2}}
</template>
<script>
import storeId from './pinia1.js'
export default {
data () {
return {
count2: storeId().count
}
},
}
</script>
正确用法:
count2具有响应性
<template>
{{count2}}
</template>
<script>
import storeId from './pinia1.js'
import { storeToRefs } from 'pinia'
export default {
data () {
return {
count2: storeToRefs(storeId()).counter
}
},
}
</script>
3. pinia中定义的每个store,id必须不同
vuex:
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:
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()
}
}
})
参考: