vuex学习
一:Vuex是做什么的?
官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
1.什么是状态管理?
简单来讲就是把需要的多个组件的变量全部存放在一个对象中
然后,将这个对象放在顶层的Vue实例中,让其它组件可以使用,那么是不是多个组件都可以共享这个对像中的属性了
那不是很简单吗,我们也可以自己封装一个对象来管理吗?
当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
2.管理什么状态呢?
有什么状态时需要我们在多个组件间共享的呢?
比如用户的登录状态、用户名称、头像、地理位置信息等等
比如商品的收藏、购物车中的物品等等
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
二:Vuex的基本使用
这里我们用一个小小的对const变量进行全局管理来演示Vuex的基本使用
1.先安装vuex 使用npm安装 npm install vuex --save
2.在src文件夹下创建一个store文件夹里面再创建一个index.js
里面的代码
1 import Vuex from 'vuex' 2 import Vue from 'vue' 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state:{ 8 count:0 9 }, 10 mutations:{ 11 add(state){ 12 state.count ++; 13 }, 14 att(state){ 15 state.count --; 16 } 17 } 18 }) 19 20 export default store
2.然后就要在main.js中挂载store这个对象了
1 import Vue from "vue"; 2 import App from "./App.vue"; 3 import store from './store' 4 5 Vue.config.productionTip = false; 6 7 new Vue({ 8 render: h => h(App), 9 store 10 }).$mount("#app");
3.接下来就是怎么样使用这个store了
因为这个store对像是挂载到mian.js中的,所以,是所有的组件都是能拿到这个对像的
1:通过this.$store.state.属性的方式来访问状态
2:通过this.$store.commit('mutation中方法')来修改状态
这里的注意点是
1:我们通过提交mutation的方式,而非直接改变store.state.count
2:这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值
1 <template> 2 <div id="app"> 3 <p>{{count}}</p> 4 <button @click="add()">+1</button> 5 <button @click="att()">-1</button> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 name: "app", 12 components: { 13 }, 14 computed: { 15 count(){ 16 return this.$store.state.count 17 } 18 }, 19 methods: { 20 add(){ 21 this.$store.commit('add'); 22 }, 23 att(){ 24 this.$store.commit('att'); 25 } 26 } 27 28 }; 29 </script> 30 31 <style> 32 </style>
这样就能通过点击+1 和-1 按钮来使count这个数值发生变化了
三:Vuex核心的5个概念
前面的演示就是vuex的基本操作了,但是仅仅只是这些吗?
Vuex有5个核心概念
state
getter
mutation
action
moudule
接下来我在后面逐一详细解析
四:state单一状态树
Vuex提出使用单一状态树, 什么是单一状态树呢?
英文名称是Single Source of Truth,也可以翻译成单一数据源
但是,它是什么呢?我们来看一个生活中的例子
我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。
这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。
这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。
这个和我们在应用开发中比较类似:
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
所以Vuex也使用了单一状态树来管理应用层级的全部状态。
单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
五:getters
我们先来看一个例子
在vuex中存放了小动物的信息
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state: { 8 animal: [ 9 { id: 1, name: "cat", age: 10 }, 10 { id: 2, name: "dog", age: 5 }, 11 { id: 3, name: "duck", age: 6 }, 12 { id: 4, name: "snake", age: 8 } 13 ] 14 } 15 }); 16 17 export default store;
我们的需求是获取当中小动物age>5的动物数量
不难想到我们可以在当前app.vue组件中定义一个计算属性来获取
1 <template> 2 <div id="app"> 3 <p>{{list}}</p> 4 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name: "app", 11 components: { 12 }, 13 computed: { 14 list(){ 15 return this.$store.state.animal.filter( age => age.age >=5).length 16 } 17 }, 18 methods: { 19 20 } 21 22 }; 23 </script> 24 25 <style> 26 </style>
但是只是app.vue这个组件的到了当前符合要求age的小动物的数量
那么其它的组件也要得到这个数量呢?
难道要重新在其它的组件中再写一边这个计算属性吗?
这个时候就体现这个getters这个属性的作用了
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state: { 8 animal: [ 9 { id: 1, name: "cat", age: 10 }, 10 { id: 2, name: "dog", age: 5 }, 11 { id: 3, name: "duck", age: 6 }, 12 { id: 4, name: "snake", age: 8 }, 13 ] 14 }, 15 getters:{ 16 getagecount(state){ 17 return state.animal.filter(s => s.age >=5).length; 18 } 19 } 20 }); 21 22 export default store;
app.vue组件里的代码
1 <template> 2 <div id="app"> 3 <p>{{qwe}}</p> 4 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name: "app", 11 components: { 12 }, 13 computed: { 14 qwe(){ 15 return this.$store.getters.getagecount; 16 } 17 }, 18 methods: { 19 20 } 21 22 }; 23 </script> 24 25 <style> 26 </style>
getters的操作也不止这些
当getter作为参数和传递参数
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state: { 8 animal: [ 9 { id: 1, name: "cat", age: 10 }, 10 { id: 2, name: "dog", age: 5 }, 11 { id: 3, name: "duck", age: 6 }, 12 { id: 4, name: "snake", age: 8 }, 13 ] 14 }, 15 getters:{ 16 getagecount(state){ 17 return state.animal.filter(s => s.age >=5); 18 }, 19 getagecountlength(state,getters){ 20 return getters.getagecount.length; 21 } 22 } 23 }); 24 25 export default store;
这里需要注意的是!!!
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数
比如我们希望根据ID获取用户的信息
我们希望获取id为1的animal
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state: { 8 animal: [ 9 { id: 1, name: "cat", age: 10 }, 10 { id: 2, name: "dog", age: 5 }, 11 { id: 3, name: "duck", age: 6 }, 12 { id: 4, name: "snake", age: 8 }, 13 ] 14 }, 15 getters:{ 16 getID(state){ 17 return (id)=>{ 18 return state.animal.find(s => s.id===id); 19 } 20 } 21 } 22 }); 23 24 export default store;
app.vue代码
1 <template> 2 <div id="app"> 3 <p>{{qwe}}</p> 4 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name: "app", 11 components: { 12 }, 13 computed: { 14 qwe(){ 15 return this.$store.getters.getID(1); 16 }, 17 }, 18 methods: { 19 20 } 21 22 }; 23 </script> 24 25 <style> 26 </style>
六:Mutation状态更新
Vuex的store状态的更新唯一方式:提交Mutation
这里需要注意的是再vuex对像里面定义的是mutations 多加了一个s
在之前,上面的对count进行vuex管理的案例已经演示了mutations的基本用法
但是这还不只是这个方法的全部用法
其实,mutations还能传递参数的
store/index.js里的代码
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state: { 8 count:0 9 }, 10 11 mutations:{ 12 add(state , n){ 13 state.count -=n; 14 } 15 } 16 17 }); 18 19 export default store;
app.vue相应的代码
1 <template> 2 <div id="app"> 3 <p>{{count}}</p> 4 <button @click=add()>-</button> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name: "app", 11 components: { 12 }, 13 computed: { 14 count(){ 15 return this.$store.state.count; 16 } 17 }, 18 methods: { 19 add(){ 20 this.$store.commit('add',2); 21 } 22 } 23 24 }; 25 </script> 26 27 <style> 28 </style>
这是传了一个参数的情况
但是如果参数不只是一个呢?
这个时候就可以传一个对象过去了
store/index.js里的代码
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state: { 8 count:0 9 }, 10 11 mutations:{ 12 add(state , obj){ 13 state.count -=obj.count; 14 } 15 } 16 17 }); 18 19 export default store;
app.vue里的代码
1 <template> 2 <div id="app"> 3 <p>{{count}}</p> 4 <button @click=add()>-</button> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name: "app", 11 components: { 12 }, 13 computed: { 14 count(){ 15 return this.$store.state.count; 16 } 17 }, 18 methods: { 19 add(){ 20 this.$store.commit('add',{count:2}); 21 } 22 } 23 24 }; 25 </script> 26 27 <style> 28 </style>
除此之外
Vue还提供了另外一种风格, 它是一个包含type属性的对象
将app.vue里的代码改为
1 <template> 2 <div id="app"> 3 <p>{{count}}</p> 4 <button @click=add()>-</button> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name: "app", 11 components: { 12 }, 13 computed: { 14 count(){ 15 return this.$store.state.count; 16 } 17 }, 18 methods: { 19 add(){ 20 this.$store.commit({ 21 type:'add', 22 count:2 23 }); 24 } 25 } 26 27 }; 28 </script> 29 30 <style> 31 </style>
这2种方式代码不同,但是效果是一样的
接下来就是mutations的相应规则了
Vuex的store是响应式的,当store中的数据发送改变时,Vue组件会自动更新
但是这也是要遵守一定规则的
1.提前在store中初始化好所需的属性,不然是没法直接添加或者修改的
我们在vuex中定义了一个info对象,并给他2个键值对name和age
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state: { 8 info:{ 9 name:'gsq', 10 age:18 11 } 12 13 }, 14 15 mutations:{ 16 add(state , obj){ 17 state.info["height"] -=obj.height; 18 } 19 } 20 21 }); 22 23 export default store;
我们的需求是点击按钮,给里面增加一个height属性
app.vue
1 <template> 2 <div id="app"> 3 <p>{{info}}</p> 4 <button @click=add()>更新</button> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name: "app", 11 components: { 12 }, 13 computed: { 14 info(){ 15 return this.$store.state.info; 16 } 17 }, 18 methods: { 19 add(){ 20 this.$store.commit({ 21 type:'add', 22 height:1.88 23 }); 24 } 25 } 26 27 }; 28 </script> 29 30 <style> 31 </style>
经过实验发现是更改不了的
这个时候就是第二个规则了
2.当给state中的对象添加新的属性时候,使用下面的两种方式
1.vue.set(obj,'newpoop',123)
2.用新的对像给旧对象赋值
好,我们直接上代码演示
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 4 Vue.use(Vuex); 5 6 const store = new Vuex.Store({ 7 state: { 8 info:{ 9 name:'gsq', 10 age:18 11 } 12 13 }, 14 15 mutations:{ 16 add(state , obj){ 17 //注释掉不可行的方式 18 //state.info["height"] -=obj.height; 19 20 //可行方式一:Vue.set() 21 //Vue.set(state.info,'height',obj.height); 22 23 //可行方式二:给info对象赋值一个新的值 24 state.info={...state.info,"height":obj.height} 25 26 } 27 } 28 29 }); 30 31 export default store;
接下来,我们再考虑一个问题,
当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多
方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
那么怎么来解决这个问题呢?
就要用到mutation常量类型了
我们可以创建一个mutation-type.js这个js文件来定义我们的常量
mutation-type.js
export const UPDATE_INFO = 'UPDATE_INFO'
index.js
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 import * as types from './mutation-types' 4 5 Vue.use(Vuex); 6 7 const store = new Vuex.Store({ 8 state: { 9 info:{ 10 name:'gsq', 11 age:18 12 } 13 14 }, 15 16 mutations:{ 17 //add(state , obj){ 18 //注释掉不可行的方式 19 //state.info["height"] -=obj.height; 20 21 //可行方式一:Vue.set() 22 //Vue.set(state.info,'height',obj.height); 23 24 //可行方式二:给info对象赋值一个新的值 25 // state.info={...state.info,"height":obj.height} ; 26 // } 27 //定义常量的写法 28 [types.UPDATE_INFO](state,obj){ 29 state.info={...state.info,"height":obj.height} ; 30 } 31 } 32 33 }); 34 35 export default store;
app.vue中使用
1 <template> 2 <div id="app"> 3 <p>{{ info }}</p> 4 <button @click="add()">更新</button> 5 </div> 6 </template> 7 8 <script> 9 import { UPDATE_INFO } from "./store/mutation-types.js"; 10 11 export default { 12 name: "app", 13 components: {}, 14 computed: { 15 info() { 16 return this.$store.state.info; 17 } 18 }, 19 methods: { 20 add() { 21 this.$store.commit(UPDATE_INFO,{height:1.88}) 22 } 23 } 24 }; 25 </script> 26 27 <style></style>
该有的差不都有了,那么mutation里面能不能进行异步操作呢
是不行的
常情况下, Vuex要求我们Mutation中的方法必须是同步方法
七:action
上面提到mutation不能进行异步操作
万一要进行异步操作,该怎么办呢?
首先什么是异步?
我们先来说说js的运行机制
1 console.log(1) 2 3 setTimeout(function(){ 4 5 console.log(2) 6 7 },0) 8 9 console.log(3) 10 11 这里打印顺序是 1 3 2
之所以会是这样是因为 异步代码不等待结果,直接进行下面的代码,所以定时器只是开启了,而没有立即执行里面的
代码,等到当前运行坏境的代码执行完之后再回来执行定时器里面的代码
比如网络请求,必然是异步的
Action类似于Mutation, 但是是用来代替Mutation进行异步操作的
那么action怎么用呢?
action到底是个什么东西呢?
我们先上代码
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 Vue.use(Vuex); 4 5 const store = new Vuex.Store({ 6 state: { 7 count: 0 8 }, 9 10 mutations: { 11 add(state) { 12 state.count++; 13 } 14 }, 15 actions: { 16 add(context) { 17 context.commit("add"); 18 } 19 } 20 }); 21 22 export default store;
上面的代码就是action的基本使用方式
那么问题来了
context'是什么?
context是和store对象具有相同方法和属性的对象?
也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等
但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说.
这样的代码是不多此一举了呢?
我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了.
acion的分发
那么刚刚我们使用了action的代码方法
那么,它要怎么用呢?
vuex代码
我们在action里定义了一个add方法,在在里面放了一个一次性的定时器,过2秒钟count就会加5
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 Vue.use(Vuex); 4 5 const store = new Vuex.Store({ 6 state: { 7 count: 0 8 }, 9 mutations: { 10 add(state, obj) { 11 state.count += obj.cCount; 12 } 13 }, 14 actions: { 15 add(context, obj) { 16 setTimeout(() => { 17 context.commit("add", obj); 18 }, 2000); 19 } 20 } 21 }); 22 23 export default store;
app.vue代码
1 <template> 2 <div id="app"> 3 <p>{{ info }}</p> 4 <button @click="add()">更新</button> 5 </div> 6 </template> 7 8 <script> 9 10 export default { 11 name: "app", 12 components: {}, 13 computed: { 14 info() { 15 return this.$store.state.count; 16 } 17 }, 18 methods: { 19 add() { 20 this.$store.dispatch('add',{cCount:5}) 21 } 22 } 23 }; 24 </script> 25 26 <style></style>
当然上面的例子只是模仿网络请求的方式,
最主要的是使用promise来操作数据
八:module
module顾名思义就是模块的意思
那么为什么要使用模块呢?
Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理
当应用变得非常复杂时,store对象就有可能变得相当臃肿.
为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutation、action、getters等
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 Vue.use(Vuex); 4 5 6 const moduleA={ 7 state:{ 8 9 }, 10 mutations:{ 11 12 }, 13 actions:{ 14 15 }, 16 getters:{ 17 18 } 19 }; 20 21 const moduleB={ 22 state:{ 23 24 }, 25 mutations:{ 26 27 }, 28 actions:{ 29 30 }, 31 getters:{ 32 33 } 34 } 35 36 37 const store = new Vuex.Store({ 38 modules: { 39 a: moduleA, 40 b: moduleB 41 } 42 }); 43 44 export default store;
来看一个例子
vuex代码
1 import Vuex from "vuex"; 2 import Vue from "vue"; 3 Vue.use(Vuex); 4 5 const moduleA = { 6 state: { 7 count: 1 8 }, 9 mutations: { 10 add(state) { 11 state.count =state.count+3; 12 } 13 }, 14 actions: {}, 15 getters: { 16 qwe(state) { 17 return state.count * 2; 18 } 19 } 20 }; 21 22 const moduleB = { 23 state: {}, 24 mutations: {}, 25 actions: {}, 26 getters: {} 27 }; 28 29 const store = new Vuex.Store({ 30 modules: { 31 a: moduleA, 32 b: moduleB 33 } 34 }); 35 36 export default store;
app.vue代码
1 <template> 2 <div id="app"> 3 <p>{{info}}</p> 4 <button @click="add()">更新</button> 5 </div> 6 </template> 7 8 <script> 9 10 export default { 11 name: "app", 12 components: {}, 13 computed: { 14 info(){ 15 return this.$store.getters.qwe; 16 } 17 }, 18 methods: { 19 add() { 20 this.$store.commit('add'); 21 } 22 } 23 }; 24 </script> 25 26 <style></style>
那么问题来啦每点击一下,count会加多少呢?
啊哈,是6
不要忘了计算属性会实施跟新的哦~~~