Vuex--vuex的相关知识

1,350 阅读9分钟

什么是vuex

vuex是专门为vue项目开发的一个状态管理工具。所谓状态,可以简单理解成变量。管理就是存储。素以vuex可以理解成一个大仓库,存放我们的变量。

为什么要使用vuex

比如说,我有个状态(变量),很多组件都要用到它,那我把这个变量存储给谁都不是,都会觉得我偏心。这时,我们就需要一个大仓库,放一些很多组件都要共享的变量。这就是我们vuex的作用:存放共享的状态,让所有的组件都能使用到它。
等等,单单是这个我觉得这个也是很好封装的对吧,我可以给Vue的原型添加一个storeObj把这些变量放在这个对象中:

let storeObj={};
storeObj.a="a";
Vue.prototype.storeObj = storeObj;

....

这样行吗,当然是不行的,很重要的原因是它不具有响应式。就是数据改变页面不会响应它的改变。所以我们不要使用自己封装的仓库,直接用vuex它不香嘛。

哪些状态需要放在vuex中

一般来讲多个组件需要共享的状态都可以放在vuex中。但是并不是所有状态都往里面塞。主要是下面几种情况

  • 用户的登录状态,多个组件多个页面都会用到它。
  • 购物车中的信息等等

使用vuex

第一步,下载vuex

vuex也是vuejs的一个插件

npm install vuex --save

运行时依赖

第二步,安装vuex

模仿router一样创建一个index.js的文件。存放配置文件

import vuex from "vuex"
import Vue from "vue"
Vue.use(vuex);

如果想在Vue中使用它,那必然要通过use方法安装

第三步,创建仓库

想想路由实例怎么创建,当然这里有一点不一样的地方他并不是

new vuex({})

而是通过

let store = new vuex.Store({});
export default store;

第三步,将仓库挂载到根实例上

import store from "./store"
new Vue({
	store
    }
})

vuex的几个核心概念

state

这个地方就是存放状态(变量)的地方。
那么问题我们的组件怎么使用共享的状态呢?
刚刚调用Vue.use方法的时候,实际上,会调用vuex.install方法,他会在Vue的原型对象prototype上绑定一个$store属性。会将这个创建的store实例赋值给这个属性,那他是怎么获取到这个store实例的呢?因为在Vue实例中我们挂载了这个store实例,他就是通过options.store获取到这个store实例的:
Vue.prototype.$store=根实例.options.store
所有的组件都继承Vue的原型,所以都会有$store属性,那我们使用仓库中状态的方法就出来了:

cpn.$store.state.变量名

例如:

const store = new Vuex.Store({
  state:{
    counter:1
  }
});
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e57a2b95cb0447f9bd1899d778289e8~tplv-k3u1fbpfcp-zoom-1.image)

单一状态树

这个概念就是说,不要创建多个仓库来分类存储变量,就创建一个仓库来存放状态,因为创建多个仓库不方便管理和维护

mutation

mutation的作用就是修改state的。并且修改state的唯一途径就是提交mutation mutation中定义一系列方法,对state进行修改,**并且这些方法第一个参数默认传入的就是state对象。**免得我们使用this获取。 例如: 那么我们怎么使用mutation呢?

cpn.$store.mutations.add()//错

这样嘛?不是的,它并不是这样,而是通过.commit的形式

cpn.$store.commit("add")//add是自定义方法

比如这里我在app.vue中使用了它: 可以看到,通过mutation对state中的状态,并且devtool给我们记录了它修改的过程。

mutation中的方法携带参数

刚刚我们说了mutation中的方法第一个形参一定是传入仓库的state属性,那如果我调用mutation的方法的时候我想传递参数怎么办呢?
比如说现在我们mutations中的add固定只能加一,那我怎么让它可以加任意数字,这个任意数字我想自己决定。
简单:我们再设置一个形参来接收参数就可以了
携带参数分为两步:

  • 定义方法的时候再设置一个形参,留个坑给她,比如这的num:
  • 在提交方法的时候,将参数传入,类似这种
cpn.$store.commit("add",5);//到时候这个5就赋值给了num

那这里我们就可以实现刚刚那个需求了: index.js: app.vue: 效果:

mutation的提交风格

其实mutation的提交存在两种形式。

  • this.$store.commit("add",num); 就是我们之前我们使用的那种形式
  • 第二种形式,commit传入一个对象:type属性,放方法名。比如我把上一种形式转换一下
	this.$store.commit({
    	type:"add",
        num:num
    })

第二种形式有一个很特别的点要注意,那就是这时候,mumation方法中的第二个形参应该是这个对象了

mutations:{
    add(state,num){//这种形式下,这个num应该是这个对象了。
      state.counter=state.counter+num;
    }

也就是说num=={type:"add",num:num},那么这时候如果你想用我们刚刚传入的num,需要这样写,num.num,所以下面的代码需要改一下

mutations:{
    add(state,num){
      state.counter=state.counter+num.num;
    }

matation的类型常量

它的作用就是为了统一mutation中方法的名字。就是你调用的时候可能命名aaa,你使用的时候也应该是aaa.要是我们不小心输入错了比如使用的时候输入了aaaa,那么commit就生效不了。所以这里就是为了统一声明和使用时方法名字的一致性。
首先要了解一点就是:

var obj = {
  ["a"]:function(){
  }
}
var obj = {
a:function(){
}
}

这两种写法其实是一样的
那我们可以对mutation中的方法进行变形。

//之前的形式
 mutations:{
    add(state,num){
      state.counter=state.counter+num;
    }
  }
  //之后的形式
   mutations:{
    ["add"](state,num){
      state.counter=state.counter+num;
    }
  }

那我们可以这样做了创建一个文件:mutation-types.js

//mutation-types.js中

export const Add  = "add";


//在store/index.js中导入
export {Add} from"./mutation-types.js"
  mutations:{
    [Add](state,num){
      state.counter=state.counter+num;
    }
  
  //在要提交这个方法的组件中导入
  export {Add} from"..../mutation-types.js"
  this.$store.commit(Add,num);

注意上面都是一些伪代码。就是简单描述一下使用过程。

修改state的方式

修改state的方式只能通过mutation修改 这是官方给出的一张图。它并没有Vue Components指向State的那条线。为什么不能这样修改呢?改是能改的,但是Devtools是一款官方推出的浏览器插件,他能追踪vuex中状态的改变。是监听不到通过这种途径的改动的,它只能监听vuex中的状态通过mutation进行的改变。(Devtools是一款官方推出的浏览器插件,他能追踪vuex中状态的改变。)

getters

getters是什么

其实getters和我们实例中的计算属性,computed。其实是差不多的。

什么时候使用getters

当我们想使用state中的变量的变形的时候,我们就可以使用getters。和我们的计算属性差不多。

怎么使用getters

  • 在创建仓库的时候,定义getters。例如:
  • 在任意组件中使用getters,例如:

使用getters的注意点

  1. getters是一个属性,不是一个函数
    这个地方有个注意点,虽然getters的属性定义的时候长得很像方法,但是它本质上是一个属性!!!并不是函数,原因我猜测它和计算属性是一个属性是一致的,如果你将他当函数使用必然会报错。看!
  2. getters的get函数其实有两个参数,但是第二个参数是可选的,第一个参数是将仓库的state属性传入,第二个参数是将仓库的getters属性传入,所以定义getters的时候,还能使用getters的其他属性定义。
 getters:{
    sqrtCounter(state){//这个函数其实可以有两个参数第二个参数是可选的,第一个参数是将仓库的state属性传入,第二个参数是将仓库的getters属性传入
      return state.counter*state.counter;
    }
  }
  1. getters本身是个属性,所以不能传递参数,如果你想要传参数,那就应该给计算属性返回一个函数。
 getters:{
    addNum(state){
      return function (a){
        return state.counter+a;
      }
    }
  }

actions

之前我们说过,修改state中状态的操作,只能放在mutations中,但是mutations中只能存在同步操作,不能存在异步操作,我们需要把异步操作分离出来,分到actions中执行。也就是说actions中都是异步操作。
那么为什么mutations中不能存在异步操作呢?
devtool这个工具是监听mutations的。如果mutations中存在异步操作它是监听不到状态改变的。所以我们需要把异步操作分离出来。举个例子,我改造一下之前mutations的代码:(加个定时器)

 mutations:{
    add(state,num){
    setTimeout(()=>state.counter=state.counter+1,100)
    }
  }

很显然这个定时器是个异步代码,所以我们有必要将他分离出来:

actions:{
	addNum(){
    	   setTimeout(()=>state.counter=state.counter+num,100)
	}
}

是不是这样分离呢?
不是的,一定要记住一句话:想要修改state中的状态必须通过mutations。所以修改状态的同步代码我们应该还是放在mutations中。所以这里要把修改代码再分离出来

	addNumber(state){
   	 state.counter=state.counter+1
    }
},
actions:{
	addNum(){
    	   setTimeout(()=>???,100)
	}
}

**接下来我们会遇到一个新的问题,我们怎么在这个actions中使用mutations中定义的方法呢? **
actions中定义的方法都会有一个形参,接收的是创建的store对象。store对象当然是通过commit来调用。所以这里会变成这样的:

mutations:{
	addNumber(state){
   	 state.counter=state.counter+1
    }
},
actions:{
	addNum(context){
    	   setTimeout(()=>context.commit("addNumber"),100)
	}
}

现在又遇到了新的问题我们怎么在组件中调用actions呢?

**在组件中,我们是使用dispatch调用actions中的方法的。**所以代码应该改造

//在store/index.js中 //在app.vue中 效果: 可以发现我们成功了,并且通过devtools可以监听到这种改变

调用actions中的方法的时候传递参数

actions中的方法,第一个形参默认是store对象,那么能不能传参数呢?我们可以再设置一个形参接收参数

actions:{
	addNum(context,num){//num作为形参接收
    	   setTimeout(()=>context.commit("addNumber"),100)
	}
}

调用actions中的方法时我只需要传入即可

this.$store.dispatch("addNumber",123);//123传给num

modules

由于vuex的原则是单一状态树,就是创建一个store仓库,存放所有的数据。那么势必会导致store仓库中存放了各种各样的状态,有时我们需要将其分一下类。这时候我们的modules就闪亮登场了。它的形式是这样的:

const store = new Vuex.Store({
	modules:{
    a:{
    state:{num:1},
    actions:{},
    mutations:{}
    getters:{}
    },
    b:{
    state:{},
    actions:{},
    mutations:{}
    getters:{}
    }...
    }
}}

就是每个模块对应一个对象,这个对象也有自己的state,actions,mutations,getters.
需要注意的点有:

  • 模块内的actions,mutations,getters,全是服务于模块中的state,比如说mutations的方法第一个参数都会传入state,那么这个state传入的应该是这个模块中的state.
  • 在组件中使用模块内的actions/getters/mutations,和之前一模一样,没有任何区别。**但是使用state的方式有点儿奇怪,应该是this.store.模块名.变量名比如这里的num:this.store.模块名.变量名 比如这里的** num:`this.store.a.num`
  • 模块中的mutations中的方法第一个形参传递的是模块中的state
  • 模块中的getters中的属性的get函数第一个参数是模块中的state,第二个参数是模块中的getters,第三个参数是rootstate,就是根上的state.
  • 模块中的actions的形参传递的是模块对应的对象。也就是我们这的属性a/b对应的值。同时它还具有一个属性rootState,能获取根对象的state。假设形参是context. 也就是说context.state对应模块的state context.actions对应模块的actions context.getters对应模块的getters context.mutations对应模块的mutations,context.rootState对应根对象的state。

结构组织

随着项目的庞大,可能最后index.js显得异常庞大,所以我们可以把getters/mutations/actions/modules的代码都抽离出来,形成这样的文件目录结构。

vuex中的state的响应式原理

之前我们就提到,在vuex仓库中的变量和data 中的数据都是响应式的,响应式的意思就是数据发生改变了。页面会立即刷新响应这种改变。

 let store = new vuex.Store({
 state:{
 counter:1,
 obj:{
 num:1,
 str:"a"
 }
 }
 })

这里我的state里面保存了两个值,一个时基础数据类型,一个是引用类型。他们全都会加到vue的响应式系统里面。引用类型的所有属性都会加到响应式系统中。也就是说obj.num/obj.str/counter他们都是响应式的。 他们都加在了响应式系统中。
但是如果我给obj添加属性的话,其实添加的属性并不具有响应式。就是数据改变了,但是不会更新到页面上。
这里我举出一个例子: state中有个obj对象,我要在mutations中定义方法更改它也就是这里的changeObj方法。 然后我在app.vue中: 首先展示state中的obj。然后点击按钮改变它。发现页面并没有更新,但是state中的obj确实被更改了,这就是不具有响应式。

如何添加属性具有响应式

需要使用Vue中的set方法。

Vue.set(要添加的对象,属性名(对象是字符串而数组是数字),属性值);

所以上述例子可以这样实现:

 changeObj(state){
      Vue.set(state.obj,"c","3");
    }

如何删除属性具有响应式

delete obj.属性名

一般的做法是这样但是这种做法不是响应式的,还是得使用Vue的delete方法。

Vue.delete(obj/arr,string/number);