vue中的状态管理vuex

vuex是什么?

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

vuex解决了什么问题

随着应用程序规模的扩大,应用程序使用的数据量也随之增长,我们会遇到俩个非常明显的问题

  1. 如何确保在不同组件中的数据保持同步
  2. 如何确保每次对于数据的修改都是可追踪的

如果上面的专业描述太过专业我们有点摸不着头脑,看一个现实中的例子,一个户外商店有两名员工,张三和李四,在一天的早上,他们分别对帐篷的数量做了一次盘点,发现一共有三个帐篷可供出售,俩个人在商店里度过了一天,张三卖出去俩个,他以为库存里还有一个,李四卖出去一个,他以为库存里还有俩个,而事实上是,库存现在已经为零了,如果他们再接受客户的预订,就会出现库存不足的情况,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映射使用

使用步骤

  1. 组件中按需导入mapState函数: import { mapState } from 'vuex'
  2. 将state中数据映射为计算属性computed:{ ...mapState(['state中的属性名']) }
  3. 把映射到组件内的数据当成一般计算属性使用

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特性总结

  1. state中定义的数据都是响应式的,只要数据发生变化,任何一个使用到当前数据的组件都会得到更新

  2. 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

使用步骤:

  1. 从vuex中导入mapMutations函数 import {mapMutations} from vuex
  2. 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分发

使用步骤

  1. 从vuex中引入mapActions函数 import {mapActions} from 'vuex'
  2. 将vuex中的actions方法映射为methods中的方法 ...mapActions(['asyncSubNum'])
  3. 当成普通的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特性总结

  1. actions函数中可以包含任何异步操作
  2. actions可以提交mutation,这也是它的最终目的
  3. actions可以访问store实例
  4. 必须使用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里关联的值
},
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值