什么是Vuex,它是干什么的
官方解释:他是一个专为Vue.js应用程序开发的状态管理模式,它采用集中式存储管理应用所有组件的状态,Vuex是响应式的。
状态管理是什么
简单的来说,就是把需要多个组件共享的变量全部存储在一个对象之中,然后将该对象放入顶层的Vue实例之中,供其它组件使用,当然我们也可以手动声明一个共享对象然后将它嵌入每一个组件中,实现方式:
const shareThing={ }
Vue.prototype.shareThing = shareThing //通过Vue原型对每一个Vue实例中的组件进行赋值,在每个组件内部只需要通过this.sharThing. 即可访问shareThing中的具体值,但该方式不支持响应式
一般来说,用户的登录信息、头像、商品信息等状态信息需要供多个组件共享使用,而这些状态就可以放入Vuex进行响应式管理。
小案例
我们实现一个计数器,同时实现两个组件共享一个counter属性
- 思路一:通过父子组件传值实现:
//子组件
<template>
<h3>{{counter}}</h3>
</template>
<script>
export default ({
name:'vuex',
props:{
counter:Number
}
})
</script>
//父组件
<template>
<div id="app">
<h3>{{counter}}</h3>
<button @click="counter++">+</button>
<button @click="counter--">-</button>
<vuex :counter='counter'/>
</div>
</template>
<script>
import vuex from './components/vuex.vue'
export default {
name: 'App',
data() {
return {
counter:0
}
},
components: {
vuex
}
}
</script>
- 思路二:通过vuex集中管理:
//vuex配置文件
// 导入
import Vue from "vue";
import Vuex from 'vuex'
// 安装插件
Vue.use(Vuex)
// 创建对象
const store = new Vuex.Store({
// 保存状态
state:{
counter:100
},
mutations:{
},
actions:{
},
getters:{
},
modules:{
}
})
// 导出store
export default store
//在main.js中引用
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store,
}).$mount('#app')
//类似于router,在这里实现了对每个组件注册$store属性,$store = store
//在组件中应用
<template>
<div id="app">
<h3>{{$store.state.counter}}</h3>
<button @click="$store.state.counter++">+</button>
<button @click="$store.state.counter--">-</button>
<vuex/>
</div>
</template>
action部分处理异步操作,mutations中处理同步操作,Devtools可以追踪哪个组件对state进行了修改
vue官方不推荐直接修改state,而是通过action和mutation对state的具体值进行修改。
vue官方提供了一个插件devtools,用于追踪Vuex中状态的变化,需要在浏览器中自行添加扩展插件。
回到之前的情景,我们不直接调用$store.state.counter来对状态中的counter的值进行更改,而是通过在配置文件中注册mutations方法,来对state中的counter进行修改,然后在每个组件中调用mutations中声明的方法。mutations中的方法都是同步方法,如果是异步操作的话,devtools将无法追踪到。
//配置文件
mutations:{
incerement(state){
state.counter++
},
decerement(state){
state.counter--
}
}
//组件调用方法
methods:{
add(){
this.$store.commit('incerement')
},
sub(){
this.$store.commit('decerement')
}
}
提交mutation是store(state)更新的唯一方式,通过commit(”)来使用mutation中的回调函数
当我们在mutation中处理数据时需要用到外部传入的数据时(例如上例中我们增加一个按钮,让其实现每按一次counter就是增加5),此时需要我们在使用commit调用回调函数时传入额外参数(payload),实现上述要求
//组件中
<button @click="addNum(5)">+5</button>
methods:{
addNum(number){
this.$store.commit('incerementNum',number)
}
//store文件中
mutations:{
incerementNum(state,number){
state.counter += number
}
},
当然除了直接使用commit进行提交,官方还提供了另外一种提交封装,一个包含type属性的对象,我们以上述例子为例
//组件中
addNum(number){
// 1.简单的提交封装
// this.$store.commit('incerementNum',number)
// 2.另一种提交封装
this.$store.commit({
type:'incerementNum',
payload
// payload是一个对象,内部属性为type:'incerementNum'和number:
})
}
//store文件中
mutations:{
incerementNum(state,payload){
state.counter += payload.number
},
}
Mutation中的处理方式是将整个commit的对象作为payload进行使用。
当然,我们还可以对Mutation中的函数进行一定处理,以便于导入调用
Mutation的响应式原理
从先前的state学习中得知,state是响应式的(当state中的数据发生改变时,Vue组件将会自动更新)也就是说,我们申明state中的对象以及其变量时,这些属性都将加入响应式系统中进行实时监听,当属性发生变化时,将会通知界面中所有用到该属性的地方,让界面进行刷新,故我们需要在store中初始化好需要的属性。如果需要改变现有的对象中的属性,可以直接在mutation中声明函数,完成该功能,然后再在组件中使用commit调用即可。当我们需要给state中的对象添加新属性时,方式有:
- 使用Vue.set(obj,’newProp’,value)
值得注意的是,state中的属性如果要加入响应式系统的话,必须提前初始化,或使用上方方法增加属性。若单纯使用js已有的在对象中添加属性的方法(state.xxxx[‘xxx’]=’xx’),则添加的属性xx不会加入响应式系统,不会被实时监听,但如果检查属性的话会发现能够实现属性的增加,但由于界面不会随之刷新,故使用该方式添加属性没有实质作用。 相应的,正确的添加方法为:Vue.set(xxxx,’xxx’,’xx’)
同样的,如果我们想要删除某个属性,直接使用js中已有的方法delete(delete state.xxxx.xxx),也无法实现在响应式系统中的删除。相应的,Vue.delete(state.xxxx,’xxx’)可以实现
单一状态树
单一状态树(single source of truth)指的是将所有的状态(state)全部保存在一个系统中,即将所有的需要进行管理的state全部保存在一个store对象里,而不是分别存于store1、store2等。如此操作有助于今后的管理与维护。
采用单一状态树,可以让我们以最直接的方式找到某个状态的片段
getters
getters,类似于computed属性,用于计算state中的值,一般用于呈现state中的值经过处理后的值,操作方式与computed基本类似。
值得一提的是,当需要使用已经在getters中定义过的值时,只需要在后续传参时传入getters,然后通过调用getters.来实现对其中值的访问(第二个传入的参数无论名称都默认为是传入getters)
getters:{
counterPower(state){
return state.count* state.count
},
fitPeople(state){
return state.People.filter(s => s.age>=20)
},
fitPeopleLength(state, getters){
return getters.fitPeople.length
}
}
回到上例,当我们想从外部传入数据而不是在内部写死筛选年龄不小于20的人时,我们需要对getters内部传入的数值进行更改。
fitPeople(state){
//返回一个函数后,便可以传入参数,调用该函数
return function(age){
return state.People.filter(s => s.age>=age)
}
}
使用箭头函数对上方函数进行进一步简化得到
fitPeople(state){
//返回一个函数后,便可以传入参数,调用该函数
return age => {
return state.People.filter(s => s.age>=age)
}
}
actions
mutations中的方法全是同步操作,如果出现异步操作的话,devtools将无法实现跟踪,很难去debug,此时需要使用action(异步操作)。
值得注意的是,在action中并不是直接对state中进行更改,而是通过commit使用mutation中的函数对state进行修改(这样操作才能让devtools实现正常追踪)
Vuex工作流程
action的使用方式与mutation差不多,使用this.$store.dispatch调用在action中已声明好的函数。值得注意的是,在action中不能直接对state中的属性或对象进行更改,而是调用mutation中的方法进行更改,具体案例如下:
//配置文件
mutations:{
updateState(state){
state.People[1].name = 'sunShineBoy'
}
},
actions:{
// context上下文
aUpdateState(context, payload){
setTimeout(() => {
context.commit('updateState');
console.log(payload);
}, 1000);
}
}
//组件
methods:{
aUpdateState(){
this.$store.dispatch('aUpdateState','我是sunshine boy')
}
}
当然,如果我们需要在异步处理完成后提醒用户已经完成异步处理的话,我们可以在dispatch传入数据时进行更改(传入一个大的对象):
//组件
aUpdateState(){
this.$store.dispatch('aUpdateState',{
msg:'我是sunshine boy',
success:()=>{
console.log('我终于成为阳光男孩辣');
}
})
}
//配置文件
aUpdateState(context, payload){
setTimeout(() => {
context.commit('updateState');
console.log(payload.msg);
payload.success();
}, 1000);
}
当然上述操作还可以通过Promise进行操作。
//配置文件
aUpdateState(context, payload){
return new Promise((resovle,reject)=>{
setTimeout(() => {
context.commit('updateState');
resovle('传入的数据')
}, 1000);
})
}
//组件
aUpdateState(){
this.$store.dispatch('aUpdateState','我是sunshine boy')
.then(res=>{
console.log('我是阳光大男孩辣!');
console.log(res);
})
}
action这个方法本身就可以返回一个Promise,在返回这个promise之后就可以在其他地方(例如dispatch中)拿到这个promise,然后在后端就可以就这加上.then等
modules
模块化
modules的核心就是模块化,将复杂的应用进行抽离,抽离出若干个小模块,并分别进行管理,如上图,若要访问定义的moduleA的状态,则需要通过this.store.getters即可。
如果想要在module中使用根(即store)的一些状态的话,只需要在传输参数的时候传入rootState.需要注意的是,模块在声明actions中的异步操作函数时使用commit,只能调用该模块中mutations中已声明的函数方法。在actions中的方法,我们会传入参数context(上下文),我们打印一下这个参数。
不难看出context中不仅包含该模块的getters和state还包含根中的getters和state,故需要访问根中的getters和state只需要通过context.rootGetters或者context.rootState即可进行访问,当然在这里也可以对对象context进行解构,传入context中所包含的并且我们所需要的参数。
- ps.只有在模块里面才会有根的概念