这里介绍的VueX是匹配Vue3的V4版本,它绝大多数功能使用都和之前基本保持一致。
先一起看一下官网对于VueX的定义:
官网定义中提到了一个现代前端中非常重要的概念状态管理 ,怎么来理解这个概念呢?
在复杂的应用中,我们会经常遇到以下的问题:
其实这些问题我们也能解决,但需要状态在组件之间反复传递,十分容易出现错误,开发和维护成本都非常高,我们为什么不把组件的共享状态抽取出来,放到一个单独的空间管理呢,这个空间可以被任意组件访问到,这其实就是VueX的设计思想
并不是所有的项目中都需要使用VueX,如果你不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果你的应用够简单,你最好不要使用 Vuex。但是如果你需要构建一个中大型应用,在组件外部管理状态往往是一个更好的选择,那么这个时候VueX便会是自然而然的选择了。
通过npm命令在项目中安装VueX
npm i vuex --save
在package.json中确认正确的版本号
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
安装好VueX后,在src目录下新建一个store文件夹,再建立一个index.js文件,我们在这个js文件中来创建我们的store容器。
import { createStore } from 'vuex'
// 创建一个新的 store 实例
const store = createStore({
state() {
return {
count: 0,
}
},
mutations: {
increment(state) {
state.count++
},
},
})
export default store
然后在入口文件main.js中引入store并使用它。这里的store和之前的router都可以理解为Vue的插件,都可以调用use方法。
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
好了,现在我们的项目中就可以通过VueX来进行全局的状态管理了,接下来我们要深入学习其中的核心概念!
npm i vuex@3
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store:store, // 可以简写
}).$mount('#app')
import Vue from 'vue'
import VueX from 'vuex'
Vue.use(VueX)
const store = new VueX.Store({
state(){
return {
count: 1,
msg:'公共状态'
}
},
mutations: {
// 定义修改数据的方法
addCount(state, payload) {
state.count += payload.a
}
}
})
export default store
{{ msg }}
store中保存的状态同样是具有响应性的,我们把应用中的公共状态统一保存到state属性中。
js
const store = createStore({
state() {
return {
count: 0, //这里的count后面就可以被应用中所有的组件共享
}
},
})
在项目中任意创建了一个Hello组件,现在希望在这个组件中取出刚才的count。由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
在组件中获取到store必须调用useStore这个方法(和vue-router类似,在Vue3的最新生态中推荐使用函数的方式来获取示例,而非this)
vue
//Hello组件中
{{ count }}
映射函数,可以将公共的状态映射到当前的组件,再在当前组件中调用就会简便很多。如果你使用的是Vue2版本的配置选项写法,使用映射函数往往非常高效。当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性:
import { mapState } from 'vuex'
export default {
// ...
computed: {
...mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
}
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
vuex中mutations里都是同步修改数据的方法, 如果希望异步修改数据,需要把对应的方法,定义在actions的对象中
但是actions不能直接修改state,最终还是要通过触发mutations来实现。
引用的方法同样可以映射
{{ msg }}
有时候我们需要从 store 中的 state 中派生出一些状态,通过计算属性即可实现,但如果有多个组件需要用到这个计算属性,我们要么复制这个计算属性,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
const store = createStore({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos (state) {
return state.todos.filter(todo => todo.done)
}
}
})
和state类似,同样可以在组件中通过store实例来读取到对应的getter。
//Hello中
let doneTodos = computed(()=>{
return useStore().getters.doneTodos
})
mapGetters 辅助函数同样可以将 store 中的 getter 映射到局部计算属性中,如果你使用配置选项仍然推荐你这么做,在setup语法糖中这样使用的意义就没那么大了。
import { mapGetters } from 'vuex'
export default {
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
])
}
}
VUE2实现
VueX规定必须通过mutation来能修改state中的状态。mutations中保存了修改了state的各种方法,在组件中可以通过store.commit来触发对应的方法,同时传入参数(这个参数叫做载荷)
//store
mutations: {
increment(state, n) {
state.count += n
},
},
//Hello
{{ count }}
{{todo.text}}
VueX是规定mutations中修改状态必须是同步的,我们可以尝试写一段异步代码:
mutations: {
increment(state, n) {
setTimeout(() => {
state.count += n
}, 2000)
},
},
大家肯定会疑问,代码没问题呀页面也能够正确响应状态的变化,但是VueX设计思想中需要能够捕捉到mutation前后状态的快照,异步代码无法让VueX能够了解其执行时机,这样描述可能大家此时仍然不好理解,所以现在你需要记住这个结论!
action和mutation有些类似,但不同在于:
Action 函数第一个参数接受一个与 store 实例具有相同方法和属性的 context对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
Action第二个参数也接受一个载荷。
actions:{
increment(ctx,payload){
setTimeout(()=>{
ctx.commit('increment',payload)
},2000)
}
}
在组件中视图中,可以通过事件来触发action。所以之前的异步操作可以通过分发action来实现。
const add = () => {
store.dispatch('increment',2)
}
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。Getter 同样也默认注册在全局命名空间。必须注意,不要在不同的、无命名空间的模块中定义两个相同的 getter 从而导致错误。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const store = createStore({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
}
}
}
})
命名空间在多人协作处理大型应用的时候还是非常具有实际意义的。
vue2实现
分别为login和profile新建store项目,可以分别为不同的组件书写不同的store
import Vue from 'vue'
import VueX from 'vuex'
import loginStore from "./loginStore";
import profileStore from "./profileStore";
Vue.use(VueX)
const store = new VueX.Store({
modules:{
login:loginStore,
profile:profileStore,
}
})
export default store
在app中可以通过模板来获取到子组件store中的值
// 代表Store中modules下,login这个store中a元素的值
{{ $store.state.login.a }}
在store中,存放需要在组件中流通的公共状态
不需要流通的状态和属性还是仍然放在原本的组件中