学习一个新的知识点,我们先问自己两个问题: 它是什么,它能解决什么问题。弄懂这两点以后,我们再去学习如何用它。首先介绍一些vuex的基本概念:
概念
官方的解释是这样的: Vuex是Vue应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
看着有些头疼,到底什么是状态管理模式,什么是集中式存储管理呢?
通俗的解释:
状态管理模式:状态可以大概理解为变量,那么就是对变量进行管理。变量管理主要包括三部分:state(数据+状态)、view(视图)、actions(行为),三者之间形成一个‘单项数据流’环,如下图:State映射到View上,View监听用户输入,触发Actions,Actions对State进行改变
集中式存储管理:将变量全部放在一起进行管理,那什么时候需要用到集中式管理呢?
看下面张图,如果组件1想访问组件2或3中的状态(数据),组件2想访问组件1、3中的状态,如果他们之间存在关系,那么可以通过props和 p a r e n t 、 parent、 parent、children等拿到对应的状态。
但是现在他们是相互独立,毫无关系的,所以我们就需要将公有访问的数据抽取出来,存储在一个共有的模块中,以一个全局单例模式进行管理,所有的组件都可以按照某种特定方式进行访问或者修改,当在某个组件中修改状态后,其他组件中该值也对应发生变化,这就是集中式存储管理
好了,那么 Vuex是什么呢?
是一种对状态集中式管理的模式,方便组件之间实现状态共享
那么Vuex解决了什么问题呢?
解决多个视图依赖同一个状态,并且在不同视图上进行状态变更后,其他视图也需要同时变更到同一个状态
Vuex的安装使用
npm install vuex --save (vuex在运行依赖和开发依赖中都需要,所以就直接–save)
使用:
新建文件夹(store)—> 创建index.js ->引入vuex
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex) //安装
//创建对象
const store = new Vuex.Store({
})
export default store //store默认导出
在main.js中引入:
import Vue from 'vue'
import App from './App.vue'
import store from './store/index' //导入
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store, //挂载
}).$mount('#app')
Vuex的核心
Vuex的核心主要下面5个部分:
state
mutations
actions
getters
modules
state
state(状态),即定义数据的地方,我们把所有公有数据定义在这里。
定义:
const store = new Vuex.Store({
state: {
counter: 10,
}
})
在组件中获取vuex状态:
直接获取
//template中
<h2>{{$store.state.counter}}</h2>
//script中
this.$store.state.counter
使用mapState辅助函数+computed计算属性:当一个组件需要获取多个状态时
import {mapState} from 'vuex'
export default {
computed: {
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
myCounte: function(state) {
return state.counter
},
myCounte1: state => state.counter, //es6的缩写形式
})
},
}
注意:Vuex采用的是单一状态树模式(SSOT,simple source of truth),这个意味着在vuex中有且仅有一个实例,所有的数据都集中一个实例中,方便我们访问。下面解释一下什么是有且仅有一个实例:像下图这样,就是多个state实例
mutations
定义同步方法的地方,也是唯一修改state值的地方,但是这个 ’唯一‘ 是相对于 devTools而言的
在介绍mutations的用法之前,我们一起来了解一下Devtools(google商店的一款插件,用来调试vue)的一个作用位置:
上面图中,可以看到 Devtools只在Mutatios作用,为了能让Devtools能够监听到数据的变化,我们应尽可能只在Mutations方法中改变state。
如果需要发送异步请求,利用返回的数据去改变state中状态的值,则需将函数定义到Actions中,然后在回调函数中通过Commit调用Mutations的方法,Devtools才能相应的监听到。
**注意:**同步和异步定义函数无论定义在哪里,我们的view都会正常的显示。但是为了方便使用Devtools调试,所以我们遵守这种规则,当然,你也有权力说我不用Devtools进行调试,那么也可以不遵守这种规则,理论上来说数据是正确的。
理解完Devtools的作用机理后,我们就应该明白 同步函数应该定义在 mutations中,而异步函数需要定义在actions中。
mutaions的用法:
每个mutations都有一个字符串的事件类型(type)和一个回调函数(handler),我们在回调函数中进行状态的更改。
定义
const store = new Vuex.Store({
state: {
counter: 1
},
mutations: {
increment (state,payload) {//第一个参数默认state,第二个参数是接收的传递过来的参数,对于第二个参数,我们把他称之为mutations的载荷
state.counter += payload
}
}
})
mutation的第一个参数默认state,第二个参数是传递过来的参数,对于第二个参数,我们把他称之为mutations的载荷
使用:
载荷可以直接传递一个值
this.$store.commit('increment',20)
载荷也可以是一个对象:
//方式一
this.$store.commit('increment',{ //传递对象
counter: 20
})
//方式二,我们可以将 ’increament‘也包含到对象中传递过去,也能识别到,但是需要type标签
this.$store.commit({
type: 'increment',
counter: 20
})
当然,传递过去的是对象,那么在 payload中接收到的也是对象,所以如果我们要访问counter的话,需要用 payload.counter
mutations: {
increment (state,payload) {
state.counter += payload.counter
}
}
actions
上面我们了解到了,actions和mutaions类似,但是actions是定义异步方法的地方,actions要改变状态的话,需要用context.commit 提交一个mutation。
用法:
先来一个简单的用法,因为我没有接口去调用,所以就用setTimeout模仿异步请求接口的效果吧
定义:
actions: {
incrementAction (context,payload) {
setTimeout(()=>{ //setTimeout异步加载
console.log(payload.message);
context.commit('increment',payload.conuter) //commit到mutations
},1000)
}
}
和mutations相似,actions也有两个参数:第一个是默认参数context,我们把他上下文,它代表的是$store,第二个参数就是我们传递过去的负荷
使用:
和mutations一样也可以直接传递一个参数,也可以传递一个对象
//直接传参
this.$store.dispatch('incrementAction',20)
//传递对象
this.$store.dispatch('incrementAction',{
counter:20
})
//将 ’increament‘也包含到对象中传递过去
this.$store.dispatch({
type: 'incrementAction'
counter:20
})
简单的会了,我们再来一个稍微复杂的:
actions通常是异步的,那么我们如何能知道action什么时候结束呢?两种方法:
1 再调用actions的地方,通过返回对象执行回调(这里用词不是很精准,你们就看代码吧,代码中详细注释,自己能调用一下最好)
定义:
actions: {
incrementAction2(context,payload) {
setTimeout(()=>{
console.log(payload()); //回调执行调用过来的函数
},1000)
},
incrementAction3(context,payload) {
setTimeout(()=>{
const sussMsg = '执行成功返回数据!';//模拟请求接口成功后返回的数据
payload.success(sussMsg) //将数据传递给success函数
context.commit('addPropertyLf',payload.counter)
},1000)
}
}
调用:
methods: {
changeCounter2() {//调用actions,通过返回函数执行回调
this.$store.dispatch('incrementAction2',() => '我已经执行成功了')
},
changeCounter3() {//调用actions,通过返回带函数的对象执行回调
this.$store.dispatch('incrementAction3',{
counter: 30,
success: (data) => { //将函数赋给success
console.log(data); //打印异步请求成功后的返回数据
return "执行成功了"
}
})
}
}
2 使用promise,再定义actions时返回一个primise,然后再 store.dispath处接收action的返回值
定义:
actions: {
incrementAction4(context,payload){
return new Promise((resolve,reject) => { //通过promist来管理异步加载,然后将promise作为返回值
setTimeout(()=>{
console.log('INCREMENTACTION3在这里执行');
resolve('我是回调返回值')
},1000)
})
},
}
使用:
methods: {
changeCounter4(){//采用优雅的promise来管理异步加载
this.$store.dispatch('incrementAction4','采用promise').then(data =>{
console.log(data)
})
}
}
前面的this.$store.dispatch(‘incrementAction4’,‘我是采用promise的’)执行后返回的一个 promise对象,所以接着调用then(data => {}),来对异步请求返回的数据进行逻辑处理
getters
getters和组件中的computed原理相同,如果要对状态执行一些复杂的操作,就可以在getter中执行
用法:
getter默认两个参数,第一个是state,第二个是getters,我们可以通过第一个参数获取状态,通过第二个参数或者getters中的其他方法,可以互相进行调用,比如下面的例子
定义:
const store = new Vuex.Store({
state: {
students: [{name: 'lf', age: 18, sports: 'football'},
{name: 'ly', age: 19, sports: 'basketball'},
{name: 'lz', age: 21, sports: 'ping-pang ball'},
{name: 'lq', age: 22, sports: 'all ball'}],
},
getters: {
//获取20岁以上的学生,返回一个数组
more20AgeList(state) { //计算属性默认的第一个参数是 state,第二个参数是 getters
return state.students.filter( stu => stu.age > 20)
},
//获取20岁以上的学生的数量
more20AgeLength(state,getters) {
return getters.more20AgeList.length //通过getters调用more20AgeList方法,计算length
},
}
}
上面的getter实现对students数组中年龄大于20的学生进行过滤,在获取20岁以上学生的数量的时候,因为我们已经在前面有求20岁以上学生的信息的方法,所以可以直接调用getters.more20AgeList获取到学生信息的数组,然后再后面加上length就可以直接算出人数了。
好的,基本方法已经会了,但是如果现在有一种需求,需要自己动态传参进getter,然后利用动态的参数进行逻辑操作应该怎么办呢?
利用闭包进行:
当我们再定义闭包的时候,返回一个带参数的函数,我们在调用的时候,虽然getters不能传参,但是我们 可以为喊闭包函数传参,上代码:根据我们动态传的年龄对数据进行筛选
定义:
const store = new Vuex.Store({
state: {
students: [{name: 'lf', age: 18, sports: 'football'},
{name: 'ly', age: 19, sports: 'basketball'},
{name: 'lz', age: 21, sports: 'ping-pang ball'},
{name: 'lq', age: 22, sports: 'all ball'}],
},
getters: {
moreAgeList(state,getters){
return function(age) { //返回一个带参函数
return state.students.filter(function(stu){ //进行过滤
return stu.age > age
})
}
},
//上面那个是很全的代码,但是看起来未免复杂,采用es6的语法简写
moreAgeList2(state,getters){
return age => state.students.filter( stu => stu.age > age)
}
}
}
采用es6语句,就看起来超级简单整洁 了
调用:
<h2>{{$store.getters.more20Age3(21)}}</h2>
采用$store.getters.more20Age3获取返回的带参函数,即这一块:
然后将21传递给function的age,并且最终return过滤后的数组。
最后,在多啰嗦一句叭,大家都知道计算属性其实室友getter和setter的吧,是可以给计算属性传进参数的,但是由于setter用的比较少,所以就省略了,然后吧getter简写了,所以我们如果想给计算属性传参,但是不想写完整的getter和setter,也可以用这种 闭包函数的形式。
modules
如果全部把所有的状态处理方法全部都写在一起,文件未免显得臃肿,所以vuex给我们提供了modules运行把store分割独立的模块,每个模块都拥有自己的state、mutatios、actions、getters、modules(不过这个嵌套子模块,不建议层级嵌套太多)。
用法:
如果把一个人一生的所有东西全部都包含在一个store对象中,就会有很多,因此我们对他进行模块分割,比如把他的爱好、工作等单独分出来作为子模块
定义:
const hobby = {
state: {
book: ["JS高级编程","vue从入门到精通"]
},
mutations: {
addBook(state,payload){
Vue.set(state.book,2,payload)
}
},
actions: { },
getters: {
//倒叙输出所有的book
reverseBook(state,getters){
return state.book.slice().reverse()
}
}
}
const work = {
state: { },
mutations: { },
actions: { },
getters: { }
}
const store = new Vuex.Store({
state: {
name: "lf",
age: 18
},
modules: {
work,
hobby
}
})
子模块中state的获取、getters的调用、mutations的调用
<h2>{{$store.state.hobby.book}}</h2>
<h2>{{$store.getters.reverseBook}}</h2>
<button @click="addJava">点我增加book</button>
<script>
export default {
methods: {
addJava() {
this.$store.commit('addBook','java虚拟机')
},
}
}
注意:这里罗列一下devTools下面的数据结构:
关于子模板的对象:
以对象的形式封装进state的,所以我们在调用的时候要用 $store.state.moduleName.perpertyName的形式
关于getters和mutation:都是直接封装进去的,所以大家在子模板中给getter还有mutation取名的时候,记得不要重名,如果不小心重名,会优先在主store 中查找该方法,找到就返回,如果没有找到,就去子store 中找,如果有多个子store 的方法重名,谁先初始化出来谁的优先级就高。
记录自己学习的每一点,大家如果发现有错误的地方,请及时指正我,谢谢!