Vuex详解


之前写项目的时候发现自己对Vuex还不是那么熟练,又重新翻看了一次笔记!

Vuex

1、官方定义

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

说得直白点,vuex就是vue.js中管理数据状态的一个库,通过创建一个集中的数据存储,供程序中所有组件访问。

一个数据只要放在了vuex中,当前项目所有的组件都可以直接访问这个数据。

vuex有以下常用的几个核心属性概念:

  • State
  • Getters
  • Mutations
  • Actions
  • Modules

具体用法我们稍后揭晓。

2、实际场景

普通的父传子和子传父,或是兄弟组件之间的互传值,都是两个组件之间的数据连接,但如果数据需要多组件共享,并且数据量庞大,那么就不适宜用中央事件总线来解决。此时,我们需要一个更加强大的,能够维护庞大数据的东西,它就是vuex,我们称之为:状态管理。

3、什么时候用Vuex

是不是学了就必须要在项目中使用呢?来看官网的回答:

vuex官方文档

很显然,如果你的项目比较简单,建议还是别强行使用vuex了。

另外,值得注意的点:vuex会随着页面刷新或关闭,将所有数据恢复至最初始的状态,所以它并不能替代localStorage。

4、安装Vuex

如果你的项目是较为大型的,那么建议在创建项目时直接选择安装vuex,如果创建时并未选择安装,请参考:《官网vuex安装》。

5、State

vuex中的state类似于data,用于存放数据,只不过这个数据是所有组件公用的。

我们来实现一个计数功能,但咱们把数据存放到vuex中的state,这里是专门用来存放组件共享的数据的。来看一下怎么实现:

首先,在 store/index.js 中:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 0	// 定义了一个num
  }
})

然后在组件中:

<template>
 <div>
    <h3>{{$store.state.num}}</h3>
 </div>
</template>

如此这般,我们获取到了state中的数据:

但在html中写这么长一串,始终有点难以阅读,因此,可以在computed中获取这个值,再传入html中:

computed: {
  num(){
  	return this.$store.state.num
  }
}

// 标签中
<h3>{{num}}</h3>

那么能否在data中存储这个数据呢?比如这样:

data() {
  return {
    num: this.$store.state.num
  };
},

答案是可以的,但不太建议。因为data中的数据可以修改,但通常唯一能够修改state的方式是通过vuex中的mutations,所以此处我们了解一下即可。

6、Getters

vuex中的getters类似于computed计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

store/index.js 中:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 2
  },
  getters: {
    // 这里的参数state可以让我们快速获取到仓库中的数据
    doubleNum(state) { 
      return state.num * 2;
    }
  }
})

组件中:

<template>
 <div>
    <h3>{{num}}</h3>
 </div>
</template>
 
<script>
export default {
  computed: {
    num(){
      return this.$store.getters.doubleNum
    }
  }
};
</script>

7、Mutations

官网指出:

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

OK!那我们来修改一下这个num:

store/index.js 中(这里暂时先把getters去掉,避免大家学习混乱):

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 2
  },
  mutations: {
    // payload专业名称为“载荷”,其实就是个参数
    addNum(state, payload) {
      state.num += payload;
    }
  }
})

组件中:

<template>
 <div>
    <h3>{{num}}</h3>
    <button @click="btnClick">累加2</button>
 </div>
</template>
 
<script>
export default {
  computed: {
    num(){
      return this.$store.state.num
    }
  },
  methods: {
    btnClick(){
      // 使用commit来触发事件,第二个参数是要传递给payload的数据
      this.$store.commit('addNum', 2)
    }
  }
};
</script>

来看看效果:

有一个必须注意的点,Mutations中不允许出现异步操作,它必须是一个同步函数,来看官网的解释:

vue官方文档

我们来亲自尝试一下:

mutations: {
  addNum(state, payload) {
    // 这里给mutations添加定时器,也相当于是异步操作
    setTimeout(() => { 
      state.num += payload;
    }, 1000)
  }
},

8、Actions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 2
  },
  mutations: {
    addNum(state, payload) {
      state.num += payload;
    }
  },
  actions: {
    // context是一个对象,包含了commit和state
    AsyncAddNum(context,payload) { 
      setTimeout(() => {
        context.commit('addNum', payload)
      }, 1000)
    }
  }
})

组件中:

<template>
 <div>
    <h3>{{num}}</h3>
    <button @click="btnClick">累加2</button>
 </div>
</template>
 
<script>
export default {
  computed: {
    num(){
      return this.$store.state.num
    }
  },
  methods: {
    btnClick(){
      // dispatch是分发到意思,其实也是触发Actions中的方法
      this.$store.dispatch('AsyncAddNum', 2)
    }
  }
};
</script>

当然,上面actions中的写法有点累赘,我们还可以改写:

AsyncAddNum({ commit },payload) { 
  setTimeout(() => {
    commit('addNum', payload)
  }, 1000)
}

// 如果你还想获取state中的值,可以这样:
AsyncAddNum({ commit,state },payload) { 
  console.log(state.num);		// 2
  setTimeout(() => {
    commit('addNum', payload)
  }, 1000)
}

那么,既然actions中可以拿到state的值,能否直接改这个值,而不触发mutations呢?答案是可以的,但…还是devtool的问题,devtool并未更改值,因此,我们还是需要借助mutations来实现值的修改。

9、辅助函数

获取单个数据或触发某个方法比较容易,我们直接拿到和触发就行,但如果要获取的数据和触发的方法很多个,我们就比较麻烦了,这时候我们需要借用辅助函数。比如,我们刚刚只写了累加,现在再补充一个递减。然后来看看辅助函数怎么用。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 2,
    title: '标题'
  },
  getters: {
    doubleNum(state) {
      return state.num * 1;
    }
  },
  mutations: {
    addNum(state, payload) {
      state.num += payload;
    },
    cutNum(state, payload) {
      state.num -= payload;
    }
  },
  actions: {
    AsyncAddNum({ commit },payload) { 
      setTimeout(() => {
        commit('addNum', payload)
      }, 300)
    },
    AsyncCutNum({ commit }, payload) {
      setTimeout(() => {
        commit('cutNum', payload)
      }, 300)
    }
  }
})

此时,要拿到num和title的话,我们可以在组件中:

<template>
 <div>
    <h2>{{title}}</h2>
    <h3>{{num}}</h3>
 </div>
</template>

<script>
// 引入辅助函数mapState
import {mapState} from 'vuex'
export default {
  // 在computed中引用
  computed: {
    ...mapState(['title', 'num'])
  }
};
</script>

如果打算从getters中取出num:

<h3>{{doubleNum}}</h3>

<script>
import {mapState, mapGetters} from 'vuex'
export default {
  computed: {
    ...mapState(['title']),
    ...mapGetters(['doubleNum'])
  }
};
</script>

如果打算把mutations和actions中的方法引入:

<template>
 <div>
    <h2>{{title}}</h2>
    <h3>{{doubleNum}}</h3>
    <button @click="addNum(2)">累加2</button>
    <button @click="AsyncCutNum(2)">递减2</button>
 </div>
</template>

<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
methods: {
  	// 这里负责引入,我们把引入后的事件直接写在标签上,顺便把参数也带上
    ...mapMutations(['addNum']),
    ...mapActions(['AsyncCutNum'])
}
</script>

如果你必须把点击事件写在methods中,而不是标签上的话,你也可以这样:

<template>
 <div>
    <button @click="btnClick1">累加2</button>
    <button @click="btnClick2">递减2</button>
 </div>
</template>
 
<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
export default {
  methods: {
    ...mapMutations(['addNum']),
    ...mapActions(['AsyncCutNum']),
    btnClick1(){
      // 直接通过this.xxx来调用辅助函数引入的事件
      this.addNum(2)
    },
    btnClick2(){
      this.AsyncCutNum(2)
    }
  }
};
</script>

10、Module

假设我们把累加单独抽出来作为一个模块,在store下新建一个 add/index.js 文件:

export default {
    namespaced: true,	//	命名空间,为true时,可以在store中把当前模块文件夹名称(add),当作模块名使用
    state: {
        num: 2
    },
    getters: {
        doubleNum(state) {
            return state.num * 1;
        }
    },
    mutations: {
        addNum(state, payload) {
            state.num += payload;
        }
    },
    actions: {
        AsyncAddNum({ commit }, payload) {
            setTimeout(() => {
                commit('addNum', payload)
            }, 300)
        }
    }
}

把有关累加的所有内容,都移动至本文件。再到原来仓库index.js中的modules添加:

import add from './add'

export default new Vuex.Store({
  ...,
	modules: {
    add
  }
})

在组件中使用时,稍微有些变化:

...mapState({
  'title': 'title',
  'num1': 'num1',
  'num': state => state.add.num		// module中state值的获取方法和getters等不太一样,需要写函数形式
}),

...mapGetters({
  'doubleNum': 'add/doubleNum'
})

...mapMutations({
  'addNum': 'add/addNum'
})

11、拆分写法

实际上我们可以把state、getter、mutation、action和module都抽离出来,这样可以让store文件看着更加简洁。我们来将 store/index.js 进行拆分:

state.js

export default {
    num1: 0,
    title: '标题'
}

mutations.js

export default {
    cutNum(state, payload) {
        state.num1 -= payload;
    }
}

actions.js

export default {
    AsyncCutNum({ commit }, payload) {
        setTimeout(() => {
            commit('cutNum', payload)
        }, 300)
    }
}

modules.js

import add from './add'
export default {
    add
}

最后,在 store/index.js 中:

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import modules from './modules'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations,
  actions,
  modules
})
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TeresaPeng_zju

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值