vuex是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
vuex解决了什么问题
随着应用程序规模的扩大,应用程序使用的数据量也随之增长,我们会遇到俩个非常明显的问题
- 如何确保在不同组件中的数据保持同步
- 如何确保每次对于数据的修改都是可追踪的
如果上面的专业描述太过专业我们有点摸不着头脑,看一个现实中的例子,一个户外商店有两名员工,张三和李四,在一天的早上,他们分别对帐篷的数量做了一次盘点,发现一共有三个帐篷可供出售,俩个人在商店里度过了一天,张三卖出去俩个,他以为库存里还有一个,李四卖出去一个,他以为库存里还有俩个,而事实上是,库存现在已经为零了,如果他们再接受客户的预订,就会出现库存不足的情况,OK,听完这个故事,我们可以得知,张三和李四因为没有保持库存的数量的同步导致了尴尬,这个就是所谓的数据保持同步
,店长也需要随时知道当天是谁卖出了多少个帐篷,这个行为我们称之为数据修改是可追踪的
vuex中存什么
一般情况下,只有多个组件均需要共享的数据,才有必要存储在vuex中,对于某个组件中的私有数据,依旧存储在组件自身的data中
例如:
-
对于所有组件而言,当前登陆的用户名是需要在全体组件之间共享的,则它可以放在vuex中
-
对于文章详情页组件来说,当前的用户浏览的文章数据则应该属于这个组件的私有数据,应该要放在这个组件的data中
结论: 不要把全部的数据放在vuex中, 除非要在多个组件之间共享。
state
- state提供唯一的公共数据源,所有共享的数据都要统一放到store中的state属性中存储
首先建store文件夹,在里面建一个index.js的文件。
store/index.js
// 引入vuex
// 定义store
import Vue from 'vue'
import Vuex from 'vuex'
// 1. 以插件的格式把vuex挂载到Vue上
Vue.use(Vuex)
// 2. 创建Vuex.Store的实例
const store = new Vuex.Store({
// 各种配置(类似于data,methods,computed....)
// state就对应理解为组件中data
state: {
num: 100
}
})
// 3. 导出store实例
export default store
挂载到vue实例
接下来,在main.js中来引入上面定义好的vuex。
main.js
import Vue from 'vue'
import App from './App.vue'
// 在入口文件中引入store
import store from './store/index.js'
Vue.config.productionTip = false
// 挂载到vue的实例上
new Vue({
store: store,
render: h => h(App),
}).$mount('#app')
在组件中使用state
方法1:$store对象使用
我们在
components
目录下定义一个StoreComponent
组件,在StoreComponent
组件中演示一下通过$store使用state中数据的方法
模板中:{{ $store.state.num }}
其它配置项中:this.$store.state.num
components/AddItem.vue
<template>
<div>
通过$store拿到state中的数据:{{$store.state.num}}
</div>
</template>
<script>
export default {
mounted () {
// 其它配置项中通过this.$store获取
console.log(this.$store.state.num)
}
}
</script>
方法2:mapState映射使用
使用步骤
- 组件中按需导入mapState函数:
import { mapState } from 'vuex'
- 将state中数据映射为计算属性:
computed:{ ...mapState(['state中的属性名']) }
- 把映射到组件内的数据当成一般计算属性使用
components/SubItem.vue
// es6 按需导入
import { mapState } from 'vuex'
// console.log('mapState', mapState)
// mapState就是vuex中的一个函数
// mapState(['num']): 调用这个函数,传入实参['num']
// const rs = mapState(['num'])
// // rs就是一个对象,结构是:
// // {
// // num: function (){}
// // }
// console.log('rs',rs)
export default {
name: 'SubItem',
// 把mapState([])的结果,就是一个对象
// 把这个对象展开,合并到computed这个对象中
computed: {
...mapState(['num'])
}
}
state特性总结
-
state中定义的数据都是响应式的,只要数据发生变化,任何一个使用到当前数据的组件都会得到更新
-
state中的数据虽然可以直接修改也能成功,但是强烈不建议这么做
mutations同步修改
- 如果我们想修改state中的某个数据,我们可以通过提交mutations的方式进行修改
定义mutation
new Vuex.Store({
state:{},
mutations:{
// 函数名可以是任意合法的函数名
// 参数1:表示当前state
// 参数2:可选。它表示调用函数1时,传入的参数。
函数名1(参数1,参数2){
// 在函数内部,修改state中的数据
}
}
})
说明:
- mutations是固定写法(只有一个)
- mutations定义完成之后,就等待被调用。它的作用是用来修改vuex中的数据
store/index.js定义
// 引入vuex
// 定义store
import Vue from 'vue'
import Vuex from 'vuex'
// 1. 以插件的格式把vuex挂载到Vue上
Vue.use(Vuex)
// 2. 创建Vuex.Store的实例
const store = new Vuex.Store({
// 各种配置(类似于data,methods,computed....)
// state就对应理解为组件中data
state: {
num: 100
},
// 4. 配置mutations, 修改state数据
mutations: {
// 参数1:在这个函数被调用时,会自动传入当前state
// 参数2:可选的,它表示在调用这个函数时,额外传入的参数
// 函数名(形参参数1,形参参数2) {
// // 在函数体内部,去修改数据项
// }
// 更新
addNumFn(state){ // +1
state.num++;
},
subNumFn(state, n){ // -1
state.num = state.num - n;
},
getNumFn(state, thenum){ // 输入框
state.num = thenum;
}
}
})
// 3. 导出store实例
export default store
组件中使用mutations
方法一:通过$store提交mutations
我们在已经申明好的组件中使用$store的方式提交一下mutations
语法为:
this.$store.commit('mutaion的名字', 参数 )
App.vue
computed: {
thenum: {
set(val) {
this.$store.commit("setNumFn", val);
},
get() {
return this.$store.state.num;
},
},
},
// mutations函数名,就是在定义Vuex.store时,设置的mutations中的方法名;
// 第二个参数是可选的,它表示调用mutations时传入的额外的参数,它可以是任意数据类型。
方法二:映射使用 - 不传参
我们在已经申明好的AddItem组件中使用映射的方式提交一下mutations
使用步骤:
- 从vuex中导入mapMutations函数
import {mapMutations} from vuex
- mutaions映射为methods中的函数
...mapMutations(['setNum'])
components/AddItem.vue
import { mapMutations } from 'vuex'
// console.log(mapMutations)
const rs = mapMutations(['updateMsg'])
console.log(rs)
// rs是一个对象
// { updateMsg: function (){} }
export default {
name: 'AddItem',
methods: {
// updateMsg () {
// }
// 把mapMutations(['updateMsg'])的结果合并到
// methods对象中,相当于给methods添加了一个方法
...mapMutations(['updateMsg'])
}
}
方式三: 映射使用 - 传参
SubItem.vue中, 映射使用 - 配合传参
这里的使用与mapState比较像
mutations使用的两个细节
-
传参的个数只能是1个, 如果非要传对个, 可以放在一个对象里
store/index.js - mutations添加一个
// state是vuex里声明变量的地方 // 但是接收额外值, 接参只能是一个(n) subNumFn(state, n){ // -1 state.num = state.num - n; },
subItem.vue - 映射进去, 传参
- @click=“subNumFn(1)”
<template> <div class="box"> <h2>子组件 sub</h2> 从父组件中获取的值:<label for="">{{ num }}</label> <br> <button @click="subNumFn(1)">值-1</button> </div> </template> <style lang="css"> .box{ border:1px solid #ccc; width:200px; padding:5px; margin: 20px; } </style> <script> import { mapState, mapMutations } from 'vuex' export default { // ...其他代码 methods: { ...mapMutations(["subNumFn"]) } } </script>
-
映射成methods时可以取别名
<button @click="subFn">值-1</button>
export default { name: 'SubItem', // 把mapState([])的结果,就是一个对象 // 把这个对象展开,合并到computed这个对象中 computed: { ...mapState(['num']) }, methods: { subFn(){ this.subNumFn(1); }, ...mapMutations(["subNumFn"]) } }
actions异步修改
- mutations只能进行同步操作,而常见的异步操作要放到actions中进行,这就是我们接下来要讲的东西
定义actions
const store = new Vuex.Store({
// ...其他代码
actions: {
// context类似于$store对象
// payload是传过来的参数值
// 必须在actions里提交mutations来触发state更新(规定)
asyncSubNum(context, payload){
// 可以加入异步任务后, -> 触发mutations -> 修改state
setTimeout(() => {
context.commit("subNumFn", payload);
}, 1000);
}
}
})
组件中使用action
方法一:通过$store使用action
我们通过App.vue组件通过$store的方法进行action分发
语法:
this.$store.dispatch('action名称',payload)
App.vue
<button @click="actionsFn">actions方式修改数据, -1</button>
<script>
methods: {
actionsFn(){
this.$store.dispatch("asyncSubNum", 1)
}
}
</script>
方法二:通过映射的方式分发actions
我们通过App.vue组件通过映射的方法进行actions分发
使用步骤
- 从vuex中引入mapActions函数
import {mapActions} from 'vuex'
- 将vuex中的actions方法映射为methods中的方法
...mapActions(['asyncSubNum'])
- 当成普通的methods方法使用
App.vue
<template>
<div id="app">
<button @click="asyncSubNum(5)">actions方式修改数据-5</button>
</div>
</template>
<script>
import {mapActions} from 'vuex'
export default {
// ...其他代码
methods: {
// ...其他代码
...mapActions(["asyncSubNum"])
}
};
</script>
<style>
#app {
width: 300px;
margin: 20px auto;
border: 1px solid #ccc;
padding: 4px;
}
</style>
数据操作关系图
actions特性总结
- actions函数中可以包含任何异步操作
- actions可以提交mutation,这也是它的最终目的
- actions可以访问store实例
- 必须使用dispatch函数调用actions
getters计算属性
- 在vuex身上的全局状态-计算属性
基础使用-定义getters
store/index.js
getters: { // vuex中的计算属性, 当state改变, 重新计算并缓存
allCount(state){
// 过滤选中的统计数量
return state.goodsList.filter(obj => obj.goods_state).reduce((sum, obj) => sum += obj.goods_count, 0)
},
allPrice(state){
return state.goodsList.filter(obj => obj.goods_state).reduce((sum, obj) => sum += obj.goods_count * obj.goods_price, 0)
}
}
组件中使用getters
MyFooter.js - 使用
方法一: 通过$store使用getters
<span>合计:</span>
<span class="price">¥ {{ $store.getters.allPrice }}</span>
方法二: 通过映射的方式分发getters
// 通过vuex引入mapGetters方法(专门负责把vuex里数据导出到这里的)
import {mapGetters} from 'vuex'
export default {
// ...省略了其他代码
computed: {
...mapGetters(['allCount']) // 映射到computed身上一个allCount属性和vuex里关联的值
},
};
modules分模块
模块定义
定义用户信息和购物车信息模块, 并且放到store/index.js入口注册
store/modules/user.js
const moduleUser = {
// 每个module都是独立的, 所以这里是用函数返回对象方式
state (){
return {
username: "播仔",
nickname: "可爱的猴子"
}
},
mutations: {
changeName(state, name){
state.username = name;
}
},
actions: {
asyncChangeName(store, name){
store.commit("changeName", name);
}
},
getters: {
welcome(state){
return "欢迎" + state.username
}
}
}
export default moduleUser
store/modules/shopCar.js
import axios from 'axios'
const moduleShopCar = {
// 每个module都是独立的, 所以这里是用函数返回对象方式
state() {
return {
goodsList: [] // 列表
}
},
mutations: {
setGoodsList(state, newList) {
state.goodsList = newList
}
},
actions: {
async asyncGetGoodsList(context) {
const API_URL = `https://www.escook.cn/api/cart`
// 发送异步请求
const res = await axios.get(`${API_URL}`);
context.commit('setGoodsList', res.data.list) // 提交mutation修改state中的数据
}
},
getters: {
allCount(state) {
// 过滤选中的统计数量
return state.goodsList.filter(obj => obj.goods_state).reduce((sum, obj) => sum += obj.goods_count, 0)
},
allPrice(state) {
return state.goodsList.filter(obj => obj.goods_state).reduce((sum, obj) => sum += obj.goods_count * obj.goods_price, 0)
}
}
}
export default moduleShopCar
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import moduleUser from './modules/user'
import moduleShopCar from './modules/shopCar'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
moduleUser,
moduleShopCar
}
})
export default store
组件中使用modules下的模块
语法: $store.state.模块名.xxx变量属性
只有state取值用法发生改变了, 其余的不变
App.vue-修改
因为这里使用了state的值修改
import {mapActions, mapState} from 'vuex'
export default {
// ...其他代码
computed: {
// goodsList(){
// return this.$store.state.moduleShopCar.goodsList
// },
// 上下2种写法是等价的(重点看state是怎么取值的)
...mapState({
// 注意取值先.模块名在用里面属性
goodsList: state => state.moduleShopCar.goodsList
})
},
methods: {
...mapActions(['asyncGetGoodsList'])
},
};
9. 开启命名空间
防止多个modules之间, 如果定义的state/mutations/actions/getters里属性重名
使用命名空间, 在模块文件的根路径配置
const moduleShopCar = {
namespaced: true,
}
在store/index.js - 注册modules-key名就是空间名字
const store = new Vuex.Store({
modules: {
moduleUser: moduleUser,
moduleShopCar: moduleShopCar
}
})
修改所有使用的地方, 配合辅助函数+参数1, 快速使用命名空间
state的改变
state的改变: (key用moduleShopCar)
{{$store.state.moduleShopCar.goodsList}}
如果把state配合mapState映射到计算属性, 如下修改即可
以下这3种方式是等价的
方式1:
computed: {
goodsList(){
return this.$store.state.moduleShopCar.goodsList
},
},
方式2:
...mapState("moduleShopCar", ['goodsList']),
方式3:
...mapState({
// 带命名空间以后
goodsList: state => state.moduleShopCar.goodsList
})
mutations/actions/getters-配合函数
语法:
…mapxxxx(“命名空间”, [‘函数名’])
例如:
methods: {
...mapActions("moduleShopCar", ['asyncGetGoodsList'])
},
computed: {
...mapGetters("moduleShopCar", ['allCount']) // 映射到computed身上一个allCount属性和vuex里关联的值
},