Store 是什么?
Store
(如 Vuex
、Pinia
) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个始终存在并且每个人都可以读取和写入的组件。
它有三个概念,state
、getter
和 action
,我们可以假设这些概念相当于组件中的 data
、 computed
和 methods
。
Vue 官方推荐了两种状态管理模式,一个是Vuex
,一个是Pinia
。虽说一个是为Vue2专门开发,一个是为Vue3开发,但实际上,不管是Vue2,还是Vue3,Vuex
和Pinia
都可以使用。只不过官方目前更推荐使用Pinia
。
Pinia
由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。现有用户可能对
Vuex
更熟悉,它是 Vue 之前的官方状态管理库。由于Pinia
在生态系统中能够承担相同的职责且能做得更好,因此Vuex
现在处于维护模式。它仍然可以工作,但不再接受新的功能。对于新的应用,建议使用Pinia
。事实上,
Pinia
最初正是为了探索Vuex
的下一个版本而开发的,因此整合了核心团队关于Vuex 5
的许多想法。最终,我们意识到Pinia
已经实现了我们想要在Vuex 5
中提供的大部分内容,因此决定将其作为新的官方推荐。
注:并非所有的应用都需要访问全局状态。
一、Vuex介绍
1.Vuex的基本使用
Vuex
是vue中用来进行全局状态管理的,说的简单一点就是类似于一个全局变量,可以实现任意组件关系之间的数据读取操作,它有四个对象:state
、getters
、mutations
和actions
- state:
state
类似于vue对象中的data
,用来定义变量 - getters:
getters
类似于vue中的computed
计算属性,当state
中的数据需要经过加工后再使用时,可以使用getters
加工 - mutations:
mutations
是定义方法,整个Vuex
中只有mutations
中可以修改数据 - actions:
actions
也是定义方法,但是跟mutations
不一样的就是它可以支持异步,如果我们的数据是后端提供过来的,需要缓存,我们可以在actions
请求数据,在actions
中拿到数据调用mutations
,由mutations
去修改存储数据。
vuex数据流流程图,如下
1-1.Vue2里使用vuex
main.js引入
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
})
.$mount('#app')
store文件夹index.js
//推荐使用模块化
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name:"hello world"
},
getters: {...},
mutations: {...},
actions:{...},
modules: {...},
})
// 当前页面直接使用(不推荐直接使用,推荐使用mapState解构)
computed: {
name() {
return this.$store.state.name;
}//hello world
}
1-2.Vue3里使用vuex
main.js引入
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App)
.use(store)
.mount('#app')
store文件夹index.js
import { createStore } from 'vuex'
export default createStore({
state: {
name:"hello world"
},
getters: {...},
mutations: {...},
actions:{...},
modules: {...},
})
// 当前页面直接使用
import { useStore} from 'vuex'
const store = useStore();
const name=store.state.name //hello world
Action扩展:
Action
函数接受一个与 store
实例具有相同方法和属性的 context
对象,因此你可以调用 context.commit
提交一个 mutation
。 当你了解了Modules ,你就知道 context
对象为什么不是 store
实例本身了。
store.dispatch
可以处理被触发的 action
的处理函数返回的 Promise
,并且 store.dispatch
仍旧返回 Promise
。
以下来自官方案例:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
现在你可以:
store.dispatch('actionA').then(() => {
// ...
})
在另外一个 action 中也可以:
actions: {
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
最后,如果我们利用 async / await
,我们可以如下组合 action
:
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
一个
store.dispatch
在不同模块中可以触发多个action
函数。在这种情况下,只有当所有触发函数完成后,返回的Promise
才会执行。
小结:
组件中读取
Vuex
中的数据:$store.state.sum
组件中修改
Vuex
中的数据:$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
2.模块化(modules)的使用
2-1.单页面使用模块化
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 在同一个index页面写多个模块(modules)
const countAbout = {
namespaced:true,//开启命名空间
// state 写法一(推荐使用)
state: () => ({
sum:1,
count:1
}),
// state 写法二(推荐使用)
state() {
return {
sum:1,
count:1
};
},
//写法二 另一种写法
state:()=> {
return {
sum:1,
count:1
};
},
// state 写法三(同一页面写多个模块,不推荐使用,参考data(){})
state:{
sum:1,
count:1
},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state() {
return {
list:[],
allPerson:1
};
},
mutations: {
addCount(state,n){
state.allPerson +=n
},
},
actions: {
addPerson({state,commit},n){
commit("addCount", n);
},
},
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
export default store
2-2.多页面模块化
多页面模块( modules )引入,结构目录如下:
+ |- store // store文件夹
+ |- modules // 模块化文件夹
+ |- index1.js // 对应的功能1文件
+ |- index2.js // 对应的功能2文件
+ |- index.js // vuex 入口js文件
// 单独页面,此处可以不需要使用方法来定义state
const state = {
sum:"",
count:1
};
const getters = {
getterCount(state){
return state.count+1;
}
};
const mutations = {
maddCount(state,n){
state.count +=n
},
msumCount(state,m){
state.sum+=m
}
};
const actions = {
aaddCount({state,commit,rootState},n){
commit("maddCount", n);
},
//或是 参数n 非必需,如下
async asumCount(context){
const res = await getAllData();//getAllData为后台接口
context.commit("msumCount", res);
}
};
export default {
namespaced:true,//开启命名空间
state,
getters,
mutations,
actions
};
//store文件夹下的index.js 页面
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 自动引入store/modules
let req = require.context("./modules/", false, /\.js$/);
let myModules = req
.keys()
.map((path) => {
return {
[path.slice(2, -3)]: req(path).default //'./index1.js'.replace(/^\.\/(.*)\.\w+$/, '$1') => index1
};
})
.reduce((pre, cur) => ({ ...pre, ...cur }), {});
export default new Vuex.Store({
modules: {
...myModules
},
});
3.四个map辅助函数的使用
目标:简化获取store数据
3-1.mapState方法
映射state
中的数据为计算属性
// 官方定义
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
// 实际使用(推荐,避免当前页面使用computed,多次定义)
computed: {
//直接使用
count() {
return this.$store.state.count
}
// 借助mapState生成计算属性,有2种方法
//1. 对象写法
...mapState({sum:'sum',count:'count'}),
//2. 数组写法(推荐使用)
...mapState(['sum','count']),
},
3-2.mapGetters方法
映射getters
中的数据为计算属性
computed: {
//直接使用
count() {
return this.$store.getters.getterCount //getterCount为store里 getters的方法,但这里不需要加括号
}
// 借助mapGetters生成计算属性,有2种方法
//1. 对象写法
...mapGetters({getterCount:'getterCount'}),
//2. 数组写法
...mapGetters(['getterCount'])
},
3-3.mapActions方法
把Vuex
中的actions
的函数映射到组件的methods
中,生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数
methods:{
//正常使用
actionsAdd(n){
this.$store.dispatch("aaddCount",n) // aaddCount 是定义在store里 actions下的一个方法
}
//使用 mapActions 方法
// 1.注册 actions 的方法,有2种注册方式
// 1.1 对象形式
// 冒号左侧的 aadd 是事件函数的名称,可以自定义
// 冒号右侧的 aaddCount 是actions的名称
...mapActions({aadd:'aaddCount',asum:'asumCount'})
// 1.2 数组形式
//将 `this.aaddCount(n)` 映射为 `this.$store.dispatch('aaddCount', n)`
...mapActions(['aaddCount','asumCount'])
// 2.使用
addAll(n){//当前页面的方法
this.aadd(n)
//或
this.aaddCount(n)
}
}
3-4.mapMutations方法
把Vuex
中的mutations
的函数映射到组件的methods
中,生成与mutations
对应的方法,即:包含$store.commit(xxx)
的函数
methods:{
//正常使用
mutationsAdd(n){
this.$store.commit("maddCount",n) // maddCount 是定义在store里 mutations下的一个方法
}
//使用 mapMutations 方法
// 1.注册 mutations 的方法,有2种注册方式
// 1.1 对象形式
// 冒号左侧的 addCount 是事件函数的名称,可以自定义
// 冒号右侧的 maddCount 是mutation的名称
...mapMutations({addCount:'maddCount',sumCount:'msumCount'}),
// 1.2 数组形式
// 将 `this.maddCount()` 映射为 `this.$store.commit('maddCount')`
...mapMutations(['maddCount','msumCount']),
// 2.使用
sumAll(n){//当前页面的方法
this.addCount(n)
//或
this.msumCount(n)
}
}
备注:
mapActions
与mapMutations
使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
以上都是在Vue2中使用map方法的案例,Vue3官方文档并没有提供给我们一个好的处理方式,所以需要我们自己想办法解决。那么Vue3中如何使用辅助函数map呢?
3-5.Vue3使用辅助函数map
注:非
steup
写法,直接参考Vue2写法,这里主要讲在steup
里使用map函数
import { computed, defineComponent, onMounted } from 'vue';
import { useStore, mapState, mapGetters, mapActions, mapMutations} from 'vuex'
const store = useStore();
// 解构state
const storeStateFns = mapState(["name", "age", "sex"]);//多个state
const storeState = {};
Object.keys(storeStateFns).forEach(key => {
//需要注意的是这里需要帮函数显示绑定this,因为在setup中是没有绑定this的
const fn = storeStateFns[key].bind({ $store: store });
storeState[key] = computed(fn);
});
// 解构getters
const getterSumCount = computed(
mapGetters(['sumCount']).sumCount.bind({ $store: store })
);
// 解构mutations
const mutationSumAll = mapMutations(['sumAll']).sumAll.bind({
$store: store
});
// 解构actions
const actionSumAdd = mapActions(['sumAdd']).sumAdd.bind({
$store: store
});
4.命名空间
官方描述: 默认情况下,模块内部的
action
和mutation
仍然是注册在全局命名空间的——这样使得多个模块能够对同一个action
或mutation
作出响应。Getter
同样也默认注册在全局命名空间,但是目前这并非出于功能上的目的(仅仅是维持现状来避免非兼容性变更)。
如果你希望使用全局 state
和 getter,``rootState
和 rootGetters
会作为第三和第四参数传入 getter
,也会通过 context
对象的属性传入 action
。
若需要在全局命名空间内分发 action
或提交 mutation
,将 { root: true }
作为第三参数传给 dispatch
或 commit
即可。
modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
rootGetters['bar/someGetter'] // -> 'bar/someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
你可以通过使用 createNamespacedHelpers
创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions(['foo','bar'])
}
}
开启命名空间后,组件中读取state
数据
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','count']),
...mapState({
lists:state=>state.personAbout.list
})
开启命名空间后,组件中读取getters
数据
//方式一:自己直接读取
this.$store.getters['personAbout/bigSum']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
...mapGetters('countAbout',{bigsum:'bigSum'})
5.开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPerson',person)
//方式二:借助mapActions:
...mapActions('personAbout',{addPerson:'addPerson'})
...mapActions('personAbout',['addPerson'])
6.开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/addCount',person)
//方式二:借助mapMutations:
...mapMutations('personAbout',{addCount:'addCount'}),
...mapMutations('personAbout',['addCount']),
小结
1. // 先从vuex中结解构出四个方法
import {mapState, mapMutations, mapGetters, mapActions} from 'vuex'
2. // 在computed计算属性中映射state数据和getters计算属性
computed: {
...mapState('模块名', ['name', 'age'])
...mapGetters('模块名', ['getName'])
}
3. // 在methods方法中映射mutations和actions方法
methods: {
...mapMutations('模块名', ['方法名1','方法名2'])
...mapActions('模块名', ['方法名1','方法名2'])
}
4. //这些数据和方法都可以通过this来调用和获取
5.Vuex解决了浏览器存储什么问题
Vuex
可以监听数据的变化。当Vuex
数值发生变化时,其他组件处可以响应式地监听到该数据的变化,当数据改变时,项目中引用到该数据(并且正在监听的)的地方都会发生改变- 可以存储任意形式的数据。浏览器存储中,数据只能以字符串的形式传入,对于不是
string
格式的数据需要采用JSON.parse()
和JSON.stringify()
去读写 - 可以进行模块化存储。使用
moudle
模块化开发,可以对存储数据进行归类,避免存储内容过于臃肿
没有数据存储大小限制。Vuex
是存储在内存中的,而storage存储在本地中,有一定的存储大小限制(cookie 4K,localStorage、sessionStorage 5M)存储内容过多会消耗内存空间,导致页面变卡。
二、Pinia介绍
相比于 Vuex
,Pinia
提供了更简洁直接的 API,并提供了组合式风格的 API,最重要的是,在使用 TypeScript 时它提供了更完善的类型推导。
1.Pinia的基本使用
与 Vuex
相比,Pinia
中只有 state
、getters
、actions
,没有了mutations
和modules
,Pinia
不必以嵌套(通过modules
引入)的方式引入模块,因为它的每个store
便是一个模块,如storeA,storeB… 。
main.js引入
//vue版本为Vue3,vue2中推荐使用vuex
import { createApp } from "vue";
import App from "./App.vue";
import {createPinia} from 'pinia'
const pinia = createPinia()
createApp(App)
.use(pinia)
.mount("#app");
store文件夹index.js
import { defineStore } from "pinia";
// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。是必须传入的, Pinia 将用它来连接 store 和 devtools
export const useCounterStore = defineStore("counter", {
//为了完整类型推理,推荐使用箭头函数
state: () => {
return {
//所有这些属性都将自动推断出它们的类型
name: "hello pinia",
count:1
};
},
// 也可以这样定义
// state: () => ({ count: 0 })
getters: { ... },
actions: {
increment() {
this.count++
},
},
});
// 当前页面直接使用
import { useCounterStore } from '@/store/index.js'
const counter = useCounterStore()
console.log(counter.name); //hello pinia
counter.count++
为实现更多高级用法,你甚至可以使用一个函数(与组件 setup()
类似)来定义一个 Store
:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
在 Setup Store 中:
ref()
就是state
属性computed()
就是getters
function()
就是actions
Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store
内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂。
相比于Vuex
,Pinia
是可以直接修改状态的,并且调试工具能够记录到每一次state
的变化。
import { useCounterStore } from '@/store/index.js'
const counter = useCounterStore()
console.log(counter.name); //hello pinia
//直接修改数据
counter.name = 'hello world'
console.log(counter.name); //hello world
2.使用 Store
<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useCounterStore()
</script>
请注意,
store
是一个用reactive
包装的对象,这意味着不需要在 getters 后面写.value
,就像setup
中的props
一样,如果你写了,但不能解构它:
<script setup>
const store = useCounterStore()
// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
const { name, doubleCount } = store
name // 将始终是 "Eduardo"
doubleCount // 将始终是 0
setTimeout(() => {
store.increment()
}, 1000)
// ✅ 这样写是响应式的
// 💡 当然你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>
为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()
。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>
目前响应式失去的几个情况:
1、解构 props 对象,因为它会失去响应式
2、直接赋值reactive响应式对象
3、vuex/pinia中组合API赋值
详情查看:Vue3 解构赋值失去响应式引发的思考
3.$patch方法的使用
Pinia
使用$patch
方法。不仅支持修改单个state
状态,还可以修改多个state
中的值。同时$patch
还可以使用函数的方式进行修改状态。
import { useCounterStore } from '@/store/index.js'
const counter = useCounterStore()
console.log(counter.name); //hello pinia
console.log(counter.count); //1
// 使用 $patch 修改状态
counter.$patch({
count:storeA.count + 1
})
// 或者使用 action 代替
counter.increment()
console.log(counter.count);//2
// 使用 函数的方式 进行修改状态
counter.$patch((state) => {
state.name = 'hello world'
state.count = 2
})
4.map辅助函数的使用
我们可以使用 mapStores()
、mapState()
或 mapActions()
来定义或是修改state
。
官方描述:如果你还不熟悉 setup() 函数和组合式 API,别担心,Pinia 也提供了一组类似 Vuex 的 映射 state 的辅助函数 或是 通过映射辅助函数来使用 Pinia。你可以用和之前一样的方式来定义 Store,然后通过
mapStores()
、mapState()
或mapActions()
访问:
const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
const useUserStore = defineStore('user', {
// ...
})
import { mapState } from 'pinia'
export default {
computed: {
// 其他计算属性
// ...
// 允许访问 this.counterStore 和 this.userStore
...mapStores(useCounterStore, useUserStore)
// 允许读取 this.count 和 this.double
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
// 允许读取 this.increment()
...mapActions(useCounterStore, ['increment']),
},
}
getters
里的变量可以通过mapState
直接获取
5.重置 state
使用选项式 API 时,你可以通过调用 store
的 $reset()
方法将 state
重置为初始值。
// 页面使用
const store = useStore()
store.$reset()
在 $reset()
内部,会调用 state()
函数来创建一个新的状态对象,并用它替换当前状态。
在 Setup Stores 中,您需要创建自己的 $reset()
方法:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
三、 Vuex与Pinia的差异性
1.state 参数写法
Pinia 推荐使用 完整类型推断的箭头函数,Vuex里state的写法,参考上述内容
state: () => {
return {
// 所有这些属性都将自动推断其类型
counter: 0,
name: 'Eduardo',
isAdmin: true,
}
}
2.getters参数变更
Pinia getters没有了部分参数,如getters
, rootState
, rootGetters
3.actions可以处理同步和异步
actions里可以处理同步和异步,同时去掉了commit
,可以在页面里直接使用counter.$patch((state) => {})
替代actions
修改状态,并且是能够记录每一次state
的变化。
4.mutations和modules移除
具体差异性参考如下:
// 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()
}
}
})
组件内部使用
// 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
}
}
})
组件外部使用
// 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')
})
参考来源: