Vuex

Vuex

什么是Vuex?

概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对应用中多个组件的共享状态进行集中式管理(读/写),也是组件间通信的方式,且适用于任意组件间通信

之前想要传递数据,可以使用全局事件总线/消息订阅去实现,但是如果有很多组件都想要去读和写 某个组件中的数据,那么需要在每个组件中去绑定或触发 读/写的事件,一旦组件数过多代码就会变得很复杂,所以可以使用vuex去实现这个目的。

 

Vuex 工作原理

vuex

在Vuex中有三个对象 Actions、Mutations、State

State 里面保存的是 数据,以key-value 的形式保存

 

一个动作/函数 的执行流程:

  • 在Vue 组件中 点击了加1按钮,想让一个数字加1,但这个数字不在该组件自身,在Vuex的state里

  • 调用dispatch() 第一个参数是 要进行的动作类型,比如是”add“,第二个参数是 要加几

  • 调用了dispatch后,你执行的动作和值就来到了 Actions,Actions也是一个Object对象,里面一定会有一个“ add”,它的值是一个函数,当你传进来的 “add” 能和 Actions中的 "add" 对上,就会引起 这个函数的调用,这个函数一旦被调用就受到了传进来的 数值

  • 在 ”add“ 对应的函数中自己去调用 commit 函数 ,commit 函数 和 dispatch 函数的参数相同

  • 执行完 commit 函数后,就走到了 Mutations 对象中,它也有 ”add“ ,值也是 函数,在这个函数中 会拿到两个东西,第一个是 state 第二个是你传进来的数据,修改state 中的数据,底层会自动调用 Mutate,就会引起state 中数据的变化

  • 然后重新解析组件,渲染

在Action 中 发送 Ajax请求,当你不需要发送Ajax请求时,可以直接调用 commit 函数

上述三个对象 Actions、Mutations、State都是由Store管理的 ,调用也是通过store

 

搭建Vuex环境

1.安装Vuex

npm i vuex@3

这里我们需要安装vuex3版本,因为 vuex4只能在 vue3中使用

2.Vue.use(Vuex)

这里的注意点是 在 使用 Vue.use(Vuex) 必须在创建store 实例之前,所以需要先创建store实例

按照官网的写法,是在src目录下创建一个 store目录,在该目录下创建 index.js,在该文件中创建 store实例和使用 vuex插件

//该文件用于创建Vuex中最核心的Store
​
//引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//使用vuex 插件
Vue.use(Vuex)
​
//准备Actions--用于响应组件中的动作
const actions = {}
//准备 mutations--用于操作数据
const mutations = {}
//准备state--用于存储数据
const state = {}
​
//创建并暴露 store
export default new Vuex.Store({
    actions,
    mutations,
    state
})

3.在 main.js 引入 store,并传入 vm 中

import Vue from "vue";
import App from './App'
import store from './store/index'
 
new Vue({
    el:"#app",
    render:h => h(App),
    store,
    beforeCreate(){
        Vue.prototype.$bus = this
    }
})

 

修改 计数案例

在没有使用vuex 的时候的计数案例:

<template>
  <div>
    <h1>当前的值为:{{totalNum}}</h1>
    <select v-model="selectNum">
        <option :value="1">1</option>
        <option :value="2">2</option>
        <option :value="3">3</option>
    </select>
    <button @click="addNum">+</button>
    <button @click="decrementNum">-</button>
    <button @click="addNumOdd">奇数加</button>
    <button @click="addWait">等会加</button>
  </div>
</template>
​
<script>
​
export default {
    name:'Counts',
    data(){
        return{
            totalNum:0,
            selectNum:1
        } 
    },
    methods:{
        addNum(){
            this.totalNum += this.selectNum
        },
        decrementNum(){
            this.totalNum -= this.selectNum
        },
        addNumOdd(){
            if(this.totalNum % 2){
                this.totalNum+=this.selectNum
            }
        },
        addWait(){
            setTimeout(() => {
                this.totalNum+=this.selectNum
            },500)
        }
    }
}
</script>
​
<style scoped>
    button{
        margin-left: 5px;
    }
</style>

 

使用 Vuex来修改计数案例

按照Vuex的执行流程

  • 首先把 定义在 Counts组件中的数据 放到 store 的 state 对象中

    const state = {
        totalNum:0
    }
  • 在 Counts 组件的方法中调用 dispatch

    methods:{
            addNum(){
                this.$store.dispatch("addNumber",this.selectNum)
            },
            decrementNum(){
                this.$store.dispatch("decrement",this.selectNum)
            },
            addNumOdd(){
                if(this.$store.state.totalNum % 2){
                    this.$store.dispatch("addNumber",this.selectNum)
                }
            },
            addWait(){
                setTimeout(() => {
                    this.$store.dispatch("addNumber",this.selectNum)
                },500)
            }
    }
  • 在 store的 actions 和 mutations 中 定义 dispatch 传入的方法

    const actions = {
        addNumber(context,value){
            context.commit("ADDNUMBER",value)
        },
        decrement(context,value){
            context.commit("DECREMENT",value)
        }
    }
    //准备 mutations--用于操作数据
    const mutations = {
        ADDNUMBER(state,value){
            state.totalNum += value
        },
        DECREMENT(state,value){
            state.totalNum -= value
        },
    }
  • 在之前的模板中 的差值语法不能直接使用 totalNum,而是 用 $store.state.totalNum

如果没有网络请求或其他业务逻辑,组件中 也可以越过actions,即不写dispatch,直接写commit

 

store 中的getters 配置项

1.当state中的数据需要经过加工后再使用,并且这个加工方式需要多次复用,可以在store 中配置 getters

2.getters 中计算出来的属性,用函数表示,参数是 state,还要把getters 传给Store

const getters = {
    bigSum(state){
        return state.sum * 10
    }
}
​
export default new Vuex.Store({
    ...
    getters
})

3.在组件中使用 $store.getters.bigSum 来获取这个值

 

四个map方法的使用

当我们在组件模板中去使用 state 中的数据时,每次都需要写 $store.state.xxx

我们就可以把 xxx 作为计算属性,“$store.state" 从计算属性中传过去,这样在模板中就简化了代码

但是 这样还会产生很多的计算属性,那么这时候就可以借助 vue提供的方法生成计算属性

  1. mapState:帮助我们映射state 中的数据为计算属性

    computed:{
        //第一种写法:对象写法
        ...mapState({sum:'sum',school:'school',subject:'subject'})
        //第二种写法:数组写法
        ...mapState(['sum','school','subject'])
    }

    需要注意 数组写法,必须保证计算属性的名字和 你想要在state中拿到数据的名字一致

    mapState 前的 "..." 的含义是 把 这个方法里面的对象追加到 computed中

  2. mapGetters:帮助我们映射getters 中的数据为计算属性

    使用方法和 mapState 相同,需要注意的是这两个方法需要从vuex 中用 import引入

  3. mapActions:用于帮助我们生成与actions对话的方法,即包含$store.dispatch(xxx) 的函数

    methods:{
        //第一种写法,对象写法
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
        //第二种写法:数组写法
        ...mapAction(['jiaOdd','jiaWait'])
    }
  4. mapMutation:用于帮助我们生成与 mutations对话的方法,即包含 $.store.commit(xxx) 函数

    使用方法和 mapActions 相同

注意:mapActions和 mapMutation 使用时,若需要传递参数,在模板中绑定事件时传递好参数,否则参数就是事件对象

下面是案例:

<template>
  <div>
    <!-- <h1>当前的值为:{{$store.state.totalNum}}</h1> -->
    <!-- <h3>当前的值放大十倍为:{{$store.getters.bigNum}}</h3> -->
    <h1>当前的值为:{{totalNum}}</h1>
    <h3>当前的值放大十倍为:{{bigNum}}</h3>
    <select v-model="selectNum">
        <option :value="1">1</option>
        <option :value="2">2</option>
        <option :value="3">3</option>
    </select>
    <!-- <button @click="addNum">+</button>
    <button @click="decrementNum">-</button> -->
    <!-- 调用方法时需要传参,否则参数就是鼠标点击事件-->
    <button @click="ADDNUMBER(selectNum)">+</button>
    <button @click="DECREMENT(selectNum)">-</button>
    <button @click="addNumOdd(selectNum)">奇数加</button>
    <button @click="addWait(selectNum)">等会加</button>
  </div>
</template>
​
<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
    name:'Counts',
    data(){
        return{
            selectNum:1
        } 
    },
    computed:{
        ...mapState(['totalNum']),
        ...mapGetters(['bigNum'])
    },
    methods:{
        //这里用的都是 数组的方式,要求传入的名字要和store中 Actions和Mutations中的名字一致,使用时也是用这个名字
        ...mapActions(['addNumOdd','addWait']),
        ...mapMutations(['ADDNUMBER','DECREMENT'])
    }
}
</script>
​
<style scoped>
    button{
        margin-left: 5px;
    }
</style>
//准备Actions--用于响应组件中的动作
const actions = {
    addNumOdd(context,value){
        if(context.state.totalNum % 2){
            context.commit("ADDNUMBER",value)
        }
    },
    addWait(context,value){
        setTimeout(() => {
            context.commit("ADDNUMBER",value)
        },500)
    }
}
//准备 mutations--用于操作数据
const mutations = {
    ADDNUMBER(state,value){
        state.totalNum += value
    },
    DECREMENT(state,value){
        state.totalNum -= value
    },
}
//准备state--用于存储数据
const state = {
    totalNum:0
}
​
//准备getters--用于计算state中的数据
const getters = {
    bigNum(){
        return state.totalNum * 10
    }
}

 

Vuex模块化

目的:让代码更好维护,让多种数据分类更加明确

上面的案例只有一个组件,现在我们再创建一个组件,体现出多组件共享数据

<template>
  <div>
    <h1>人员列表</h1>
    <h3>列表中第一个人是:{{firstPerson.name}}</h3>
    <h3 style="color-red">Counts组件计算后的值:{{counts}}</h3>
    <input type="text" v-model="personName"> 
    <button @click="addPerson">添加</button>
    <button @click="addPersonWang">添加姓王的人</button>
    <ul>
        <li v-for="person in personList" :key="person.id">{{person.name}}</li>
    </ul>
  </div>
</template>
​
<script>
import {nanoid} from 'nanoid'
export default {
    name:'Persons',
    data(){
        return{
            personName:''
        }
    },
    computed:{
        personList(){
            return this.$store.state.personList
        },
        firstPerson(){
            return this.$store.getters.firstPerson
        },
        counts(){
            return this.$store.state.totalNum
        }
    },
    methods:{
        addPerson(){
            const person = {id:nanoid(),name:this.personName}
            this.$store.commit('ADD_PERSON',person)
            this.personName = ''
        },
        addPersonWang(){
            const person = {id:nanoid(),name:this.personName}
            this.$store.dispatch('addPersonWang',person)
            this.personName = ''
        }
    }
}
</script>
​
<style scoped>
    button{
        margin-left: 5px;
    }
</style>
//该文件用于创建Vuex中最核心的Store
​
//引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//使用vuex 插件
Vue.use(Vuex)
​
//准备Actions--用于响应组件中的动作
const actions = {
    // addNumber(context,value){
    //     context.commit("ADDNUMBER",value)
    // },
    // decrement(context,value){
    //     context.commit("DECREMENT",value)
    // }
    addNumOdd(context,value){
        if(context.state.totalNum % 2){
            context.commit("ADDNUMBER",value)
        }
    },
    addWait(context,value){
        setTimeout(() => {
            context.commit("ADDNUMBER",value)
        },500)
    },
    addPersonWang(context,value){
        if(value.name.indexOf('王') === 0){
            context.commit('ADD_PERSON',value)
        }else{
            alert("请输入姓王的人")
        }
    }
}
//准备 mutations--用于操作数据
const mutations = {
    ADDNUMBER(state,value){
        state.totalNum += value
    },
    DECREMENT(state,value){
        state.totalNum -= value
    },
    ADD_PERSON(state,value){
        state.personList.unshift(value)
    }
}
//准备state--用于存储数据
const state = {
    totalNum:0,
    personList:[{id:"001",name:"张三"}]
}
​
//准备getters--用于计算state中的数据
const getters = {
    bigNum(){
        return state.totalNum * 10
    },
    firstPerson(){
        return state.personList[0]
    }
}
​
//创建并暴露 store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

新添加了一个 Persons组件,我们发现 store中的代码变得越来越多,所以我们可以把它们拆成两个store,把actions、mutations、state、getters 拆分成两部分,可以继续写在 index.js中,也可以分开写在两个文件中,在index.js 中引入这两个文件

count.js 如下:

const actions = {
    addNumOdd(context,value){
        if(context.state.totalNum % 2){
            context.commit("ADDNUMBER",value)
        }
    },
    addWait(context,value){
        setTimeout(() => {
            context.commit("ADDNUMBER",value)
        },500)
    },       
}
const mutations = {
    ADDNUMBER(state,value){
        state.totalNum += value
    },
    DECREMENT(state,value){
        state.totalNum -= value
    },
}
const state = {
    totalNum:0
}
const getters = {
    bigNum(){
        return state.totalNum * 10
    },
}
export default {
    namespaced:true,
    actions,
    mutations,
    state,getters
}

person.js 如下

const actions = {
    addPersonWang(context,value){
        if(value.name.indexOf('王') === 0){
            context.commit('ADD_PERSON',value)
        }else{
            alert("请输入姓王的人")
        }
    }
}
const mutations = {
    ADD_PERSON(state,value){
        state.personList.unshift(value)
    }
}
const state = {
    personList:[{id:"001",name:"张三"}]
}
const getters = {
    firstPerson(){
        return state.personList[0]
    }
}
export default {
    namespaced:true,
    actions,
    mutations,
    state,getters
}

在这两个新的js文件中,可以看到多了一个东西是 namespaced,就是命名空间,设置值为true。默认该值是false,如果不设置为 true,在组件中传入的配置名是识别不到的

在 index.js中

//该文件用于创建Vuex中最核心的Store
​
//引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//使用vuex 插件
Vue.use(Vuex)
​
import countAbout from './count'
import personAbout from './person'
​
//创建并暴露 store
export default new Vuex.Store({
    modules:{
        countAbout,
        personAbout
    }
})

引入js文件,在 创建Store 实例时,在配置项配置 modules,里面的值是 引入的对象

 

在组件中使用分为两种情况

  • 使用mapxxx方法生成

    <script>
    import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
    export default {
        name:'Counts',
        data(){
            return{
                selectNum:1
            } 
        },
        computed:{
            ...mapState('countAbout',['totalNum']),
            ...mapGetters('countAbout',['bigNum'])
        },
        methods:{
            ...mapActions('countAbout',['addNumOdd','addWait']),
            ...mapMutations('countAbout',['ADDNUMBER','DECREMENT'])
        }
    }
    </script>

    在引用 State、Getters、Actions、Mutations时,前面要传入一个参数,就是你在创建Store时配置的对象名,如果namespaced设置为false,那么这里传入的参数就识别不到,在 模板中只能用countAbout.xxx去使用数据了

  • 使用 this.$store.xxx

    <script>
    import {nanoid} from 'nanoid'
    export default {
        name:'Persons',
        data(){
            return{
                personName:''
            }
        },
        computed:{
            personList(){
                return this.$store.state.personAbout.personList
            },
            firstPerson(){
                return this.$store.getters['personAbout/firstPerson']
            },
            counts(){
                return this.$store.state.countAbout.totalNum
            }
        },
        methods:{
            addPerson(){
                const person = {id:nanoid(),name:this.personName}
                this.$store.commit('personAbout/ADD_PERSON',person)
                this.personName = ''
            },
            addPersonWang(){
                const person = {id:nanoid(),name:this.personName}
                this.$store.dispatch('personAbout/addPersonWang',person)
                this.personName = ''
            }
        }
    }
    </script>
    • 引入 state 时,使用 this.$store.state.personAbout.personList

    • 引入getters 时,使用 this.$store.getters['personAbout/firstPerson']

    • 调用dispatch时,this.$store.dispatch('personAbout/addPersonWang',person)

    • 调用commit时,this.$store.commit('personAbout/ADD_PERSON',person)

    getters、dispatch、commit 都需要使用 namespaced/xxx的方式

posted @ 2024-07-02 16:22  GrowthRoad  阅读(9)  评论(0编辑  收藏  举报