什么是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的注意点
- getters是一个属性,不是一个函数
这个地方有个注意点,虽然getters的属性定义的时候长得很像方法,但是它本质上是一个属性!!!并不是函数,原因我猜测它和计算属性是一个属性是一致的,如果你将他当函数使用必然会报错。看! - getters的get函数其实有两个参数,但是第二个参数是可选的,第一个参数是将仓库的state属性传入,第二个参数是将仓库的getters属性传入,所以定义getters的时候,还能使用getters的其他属性定义。
getters:{
sqrtCounter(state){//这个函数其实可以有两个参数第二个参数是可选的,第一个参数是将仓库的state属性传入,第二个参数是将仓库的getters属性传入
return state.counter*state.counter;
}
}
- 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.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);