Vue2(十二):Vuex

一、Vuex

1.Vuex的环境搭建

1.1安装vuex

安装:npm i vuex@3
注意Vue2一定要安装vuex3,如果是vue3可以直接npm i vuex安装的是vuex4,可以去package.json文件里看下vue的版本是啥,千万别输错了,不然会陷入痛苦的报错……

1.2.创建store文件

创建文件:src/store/index.js

在此文件中引入插件并使用vuex插件,使用vuex插件必须在引入store之前,如果在main.js中引入和使用vuex的话,由于js文件里所有的import语句都会提升到最开始执行,所以会报错滴。总结:引入store必须在Vue.use(Vuex)之后

//该文件用于创建Vuex中最为核心的store

//引入Vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex';//引入插件并使用插件
Vue.use(Vuex); //使用插件后就可以在vm,vc里使用store配置项

//准备actions,用于响应组件中的动作
const actions = {};

//准备mutations,用于操作数据(state)
const mutations = {};

//准备state,用于存储数据
const state = {};

//创建store
const store = new Vuex.Store({
    actions: actions,
    mutations, //简写
    state //简写
});

//导出store
export default store;

1.3.main.js引入store

JS执行的时候会把import提升到顶部,与摆放顺序无关,如果放在main.jsimport store from './store' 无论放到哪里都会比Vue.use(Vuex)先执行,要想把 Vue.use(Vuex) 要放到实例化之前只有放进index.js

//js文件里所有的import语句都会提升到最开始执行
// 引入Vue
import Vue from 'vue';
// 引入App
import App from './App.vue';
Vue.config.productionTip = false;

//引入store
import store from './store/index.js';

// 创建一个Vue实例
new Vue({
    el: '#app',
    store: store,  //或者直接写store
    render: h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this; //创建全局事件总线
    }
});

这样的话,vm和所有vc就都能碰到$store

2.Vuex的工作原理

2.1看图说话

Vue2(十二):Vuex_第1张图片

这个工作流程用一个通俗的例子来解释:VC就是顾客,actions就是服务员,mutations就是厨师,state就是菜的原料。顾客(VC)来到店里,先告诉(dispatch)服务员(actions)要吃啥菜,然后服务员把菜单给(commit)厨师(mutations),完了之后厨师对菜的原料(state)来个加工(mutate),最后再给(render)顾客(VC)

2.2用例子来解释工作原理

使用vuex实现求和案例

Vue2(十二):Vuex_第2张图片

1、首先应该把求和数据给 vuex的state对象

const state = {
    sum: 0,  //初始化数据
};

2、页面上插值语法就该用

<h1>当前求和为:{{  $store.state.sum  }}h1>
//插值语法可以不用this

3、在组件中的回调就可以用dispatch发给actions(点菜),注意如果没有业务逻辑,直接commit可以跳过actions直接给mutations

add() {
           this.$store.dispatch('jia', this.addnum);
           //如果没有业务逻辑,直接commit可以跳过actions直接给mutations
           //this.$store.commit('JIA', this.addnum);
        },

4、在actions中使用jia这个函数来接(处理顾客需求),并且使用commit发给mutations(需求告诉厨师)。这里jia函数会接到两个参数,第一个参数是浓缩版的$store,方便你在这里调用commit把东西给mutations,第二个参数是组件那边传过来的数据。

jia(context, value) {
        //第一个参数是浓缩版的$store,方便你在这里调用commit把东西给mutations
        //第二个参数是传过来的数据
        context.commit('JIA', value); 
    },

5、在mutations中使用JIA这个函数来接(做菜),一般都写成actions对应函数的大写,意思是mutations更nb。也是接两个参数,第一个参数是state对象,第二个参数是传过来的数据。

JIA(state, value) {
    //第一个参数是state对象,第二个参数是传过来的数据
    console.log('mutations中的JIA被调用了 ', state, value);
    state.sum += value;
},

6、最后通过state中匹配的setter,实现响应式,把数据更新到页面(上菜)。

2.3Count.vue文件

<template>
    <div>
        <h1>当前求和为:{{  $store.state.sum  }}</h1>
        <select v-model.number="addnum">
            <option value="1" checked>1</option><!-- 不写冒号就是字符串但可以v-model.number -->
            <option value="2">2</option> <!-- 不写冒号就是字符串但可以v-model.number -->
            <option value="3">3</option> <!-- 不写冒号就是字符串但可以v-model.number -->
        </select>
        <button @click="add">+</button>
        <button @click="subtract">-</button>
        <button @click="oddAdd">当前求和为奇数再加</button>
        <button @click="waitAdd">1秒再加</button>
    </div>
</template>

<script>
export default {
    name: 'Count',
    props: ['title'],
    data() {
        return {
            addnum: 1
        }
    },
    methods: {
        add() {
            //如果没有业务逻辑,直接commit给mutations
            this.$store.commit('JIA', this.addnum);
        },
        subtract() {
            this.$store.commit('JIAN', this.addnum);
        },
        oddAdd() {
            //如果有业务逻辑,先dispatch给actions,再commit给mutations
            this.$store.dispatch('oddAdd', this.addnum);
        },
        waitAdd() {
            this.$store.dispatch('waitAdd', this.addnum);
        }
    },
};
</script>

4.store/index.js文件

其实actionscontext也可以调用dispatch传给下一个actions中的方法,用于逻辑很复杂的情况,要一个一个传,这样层次分明,等处理差不多了再传给mutations

//该文件用于创建Vuex中最为核心的store
//引入Vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex';//引入插件并使用插件
Vue.use(Vuex); //使用插件后就可以在vm,vc里使用store配置项

//准备actions,用于响应组件中的动作,写业务逻辑
const actions = {
    oddAdd(context, value) {
        //第一个参数是浓缩版的$store,方便你在这里调用commit把东西给mutations
        //第二个参数是传过来的数据
        console.log('处理了一些逻辑:oddAdd');
        context.dispatch('demo1', value); //如果逻辑多,可以多层往下传(第一个服务员)
    },
    demo1() {
        console.log('处理了一些复杂的逻辑:demo1');
        context.dispatch('demo2', value); //如果逻辑多,可以多层往下传(第二个服务员)
    },
    demo2() {
        if (context.state.sum % 2)
            context.commit('JIA', value); //传到这(第三个服务员),再提交给mutations
    },
    waitAdd(context, value) {
        setTimeout(() => {
            context.commit('JIA', value);
        }, 1000);
    },
};

//准备mutations,用于操作数据(state)
const mutations = {
    JIA(state, value) {
        //第一个参数是state对象,第二个参数是传过来的数据
        console.log('mutations中的JIA被调用了 ', state, value);
        state.sum += value;
    },
    JIAN(state, value) {
        state.sum -= value;
    }
};

//准备state,用于存储数据
const state = {
    sum: 0,  //初始化数据
};

//创建并导出store
export default new Vuex.Store({
    actions: actions,
    mutations, //简写
    state //简写
});

3.一些配置项

3.1getters配置项

1、概念:当state中的数据需要经过加工后再使用时,可以使用getters加工,类似Vue中的计算属性computed。
2、使用:在store\index.js中追加getters配置,写函数,页面读的时候读的是返回值,这点其实也和计算属性很像。

......
//准备 getters ---用于将state中的数据进行加工
const getters = {
	bigSum(state){
		return state.sum * 10
	}
}

//创建并暴露store
export default new Vuex.Store({
	......
	getters
})

3、组件中读取数据:$store.getters.bigSum

当前求和放大十倍后为:{{ $store.getters.bigSum }}

4、其实state就类似于datagetters就类似computed

2.mapstate与mapGetters

首先导入四个map方法 import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'

mapstatemapGetters用到computed里,mapMutations,mapActions用到methods

要说之前啊,我们要往页面上放state中的数据,还得$store.state.xxx,或者 $store.getters.xxx

<h1>当前求和为:{{  $store.state.sum  }}</h1>
<h1>当前求和放大十倍后为:{{  $store.getters.bigSum  }}</h1>
<h1>我在{{  $store.state.school  }}学习{{  $store.state.subject  }}</h1>

真的是非常麻烦啊,想简单点写,就可以用一下子计算属性:

computed: {
    //靠程序员亲自写计算属性来实现state插值语法编码方便
    sum() {
          return this.$store.state.sum;
      },
      school() {
          return this.$store.state.school;
      },
      subject() {
          return this.$store.state.subject;
      },
      bigSum() {
         return this.$store.getters.bigSum;
     }
},

诶这样就可以这么写了:

<h1>当前求和为:{{  sum  }}h1>
<h1>当前求和放大十倍后为:{{  bigSum  }}h1>
<h1>我在{{  school  }}学习{{  subject  }}h1>

但是实际上computed这些东西复用性很差,vuex给我们提供了一个mapStatemapGetters方法:用于帮助我们把stategetters中的数据映射为计算属性,具体写法是下面这样滴,用到了扩展运算符,先复习一下扩展运算符:

let obj1 = {x:100, y:200};
let obj2 = {
    a:1,
    ...obj1,
    b:2
}
console.log(obj2);  //{a: 1, x: 100, y: 200, b: 2}

然后下面这样写就欧了,这里注意对象写法可以任意起名,键对应计算属性方法名,值对应state中的数据名,如果方法名和数据名一样,就可以用数组形式简写

computed: {
    //靠mapState函数来实现上面的代码(对象写法)sum: 'sum'没有简写,sum: sum才可以简写
     ...mapState({ sum: 'sum', school: 'school', subject: 'subject' }),
    //靠mapState函数来实现上面的代码(数组写法)
    ...mapState(['sum', 'school', 'subject']),

    // 靠mapGetters函数来实现上面的代码(对象和数组写法)
     ...mapGetters({bigSum: 'bigSum'}),
    ...mapGetters(['bigSum'])
},

3.mapActions与mapMutations

要搁以前呐,我们这个要从组件直接用commit传数据给mutations,或者用dispatch传给actions,都需要在methods里配置:

methods: {
    //程序员费老大劲写的传mutations代码
     add() {
          //如果没有业务逻辑,直接commit给mutations
          this.$store.commit('JIA', this.addnum);
      },
      subtract() {
          this.$store.commit('JIAN', this.addnum);
      },

    //程序员费老大劲写的传actions代码
      oddAdd() {
          //如果有业务逻辑,先dispatch给actions,再commit给mutations
          this.$store.dispatch('oddAdd', this.addnum);
      },
      waitAdd() {
          this.$store.dispatch('waitAdd', this.addnum);
      },
},

但是如今有了mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数,还有mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数。

也是有两种写法, 对象写法中,键是方法名,值是传给mutations或者actions方法名,如果方法名和传的方法名一样可以简写为数组形式,一些细节见注释

methods: {
	//使用mapMutations实现程序员费老大劲写的代码联系mutations
     ...mapMutations({ add: 'JIA', subtract: 'JIAN' }),
    //如果键值名字一样,可以用数组简写,记得页面也要同步
    //...mapMutations(['JIA', 'JIAN']),

    //使用mapActions实现程序员费老大劲写的代码联系actions
    ...mapActions({ oddAdd: 'oddAdd', waitAdd: 'waitAdd' }),
    // ...maoActions(['oddAdd', 'waitAdd']),
},

备注:mapActionsmapMutations使用时,其实创建的程序员费老大劲写的代码是不一样的,这两个玩意儿创建的东西是不带数据的,它创建的是这个:

 add(value) {
     this.$store.commit('JIA', value);
 },

若需要传递参数value,需要:在模板中绑定事件时传递好参数,否则参数value是事件对象。

<button @click="increment(n)">+button>
<button @click="decrement(n)">-button>
<button @click="incrementOdd(n)">当前求和为奇数再加button>
<button @click="incrementWait(n)">等一等再加button>

##4.多组件共享数据

就是各个组件都能拿到state里的数据,然后用来用去了,很方便

Vue2(十二):Vuex_第3张图片

Vue2(十二):Vuex_第4张图片

5.Vuex模块化+命名空间(项目常用)

好难理解。。

1.这是啥

如果我们写的state,actions什么的是服务于多个种类的,比如有管加法的,有管人员的,这样放到一起很乱,所以可以把它们拆开

2.目的

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

3.使用方式:

可以都写到index.js里,也可以每个命名空间分别拆成多个js文件

const countAbout = {
namespaced:true,//开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {...}
}
}

const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}

const store = new Vuex.Store({
modules: {
 countAbout,
 personAbout
}
})

(1)开启命名空间后,组件中读取state数据
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']), 
// 前边加个参数,意思是读取countAbout 里面的 sum,school.....

(2)开启命名空间后,组件中读取getters数据
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName'] //很奇葩
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])  

(3)开启命名空间后,组件中调用dispatch

如果不写namespaced则直接写addPersonWang就可以,但是开启了命名空间,必须要加上这个名字在前边,否则会报[vuex] unknown action type: addPersonWang的错误,而且前边这个名字必须和Vuex.Store({})配置项中的名字一致。

//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

(4)开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

4.冷静,读读代码吧

(1)index.js
//引入Vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex';//引入插件并使用插件
Vue.use(Vuex);

//关于计数的相关配置
import countAbout from './Count'

//关于人员的相关配置
import personAbout from './Person'


//创建并导出store
export default new Vuex.Store({
    modules: {
        countAbout: countAbout,
        personAbout  //重名简写 
    }
});
(2)Count.js
export default {
    namespaced: true,
    state: {
        sum: 0,  //初始化数据
        school: 'HNUST',
        subject: '前端',
    },
    getters: {
        bigSum(state) {
            return state.sum * 10;
        }
    },
    actions: {
        oddAdd(context, value) {
            //第一个参数是浓缩版的$store,方便你在这里调用commit把东西给mutations
            //第二个参数是传过来的数据
            context.commit('JIA', value);
        },
        waitAdd(context, value) {
            setTimeout(() => {
                context.commit('JIA', value);
            }, 1000);
        },
    },
    mutations: {
        JIA(state, value) {
            //第一个参数是state对象,第二个参数是传过来的数据
            console.log('mutations中的JIA被调用了 ', state, value);
            state.sum += value;
        },
        JIAN(state, value) {
            state.sum -= value;
        },
    }
}
(3)Count.vue

这里边有个在actions里发送ajax请求,可以研究研究

<template>
    <div>
        <h1>当前求和为:{{  sum  }}</h1>
        <h2>当前求和放大十倍后为:{{  bigSum  }}</h2>
        <h3>我在{{  school  }}学习{{  subject  }}</h3>
        <select v-model.number="addnum">
            <option value="1" checked>1</option><!-- 不写冒号就是字符串但可以v-model.number -->
            <option value="2">2</option> <!-- 不写冒号就是字符串但可以v-model.number -->
            <option value="3">3</option> <!-- 不写冒号就是字符串但可以v-model.number -->
        </select>
        <button @click="JIA(addnum)">+</button>
        <button @click="JIAN(addnum)">-</button>
        <button @click="oddAdd(addnum)">当前求和为奇数再加</button>
        <button @click="waitAdd(addnum)">1秒再加</button>

        <h2 style="color:red">Count里边读personList</h2>
        <ul style="color:red">
            <li v-for="p in personList" :key="p.id">{{  p.name  }}</li>
        </ul>
    </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';
export default {
    name: 'Count',
    data() {
        return {
            addnum: 1
        }
    },
    computed: {
        personList() {
            return this.$store.state.personAbout.personList;
        },
        ...mapState('countAbout', ['sum', 'school', 'subject']),
        ...mapGetters('countAbout', { bigSum: 'bigSum' }),
    },
    methods: {
        ...mapMutations('countAbout', ['JIA', 'JIAN']),
        ...mapActions('countAbout', { oddAdd: 'oddAdd', waitAdd: 'waitAdd' }),
    },
    mounted() {
        console.log(this.$store)
    }
};
</script>

(4)Person.js
import axios from 'axios';
import { nanoid } from 'nanoid';
export default {
    namespaced: true,
    state: {
        personList: [
            { id: 1, name: 'zzy' }
        ]
    },
    getters: {
        firstPersonName(state) {
            return state.personList[0].name;
        }
    },
    actions: {
        addPersonHan(context, value) {
            if (value.name.indexOf('韩') === 0) {
                context.commit('ADD_PERSON', value);
            } else {
                alert('添加的人不姓韩!');
            }
        },
        //发送ajax请求拿到名字
        addPersonServer(context) {
            axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then(
                response => {
                    context.commit('ADD_PERSON', { id: nanoid(), name: response.data });
                },
                error => {
                    console.log(error.message);
                }
            )
        }
    },
    mutations: {
        ADD_PERSON(state, value) {
            state.personList.unshift(value);
        }
    }
}
(5)Person.vue
<template>
    <div>
        <h2>Person里边读personList</h2>
        <input type="text" placeholder="请输入名字" v-model="name">
        <button @click="addPerson">添加</button>
        <button @click="addPersonHan">添加一个姓韩的人</button>
        <button @click="addPersonServer">随机添加一个名字</button>
        <h2>第一个人的名字:{{  firstPersonName  }}</h2>
        <ul>
            <li v-for="p in personList" :key="p.id">{{  p  }}</li>
        </ul>
        <h2 style="color:red">Person里读sum:{{  add  }}</h2>
    </div>
</template>

<script>
import { nanoid } from 'nanoid';
export default {
    name: 'Person',
    data() {
        return {
            name: ''
        }
    },
    computed: {
        personList() {
            return this.$store.state.personAbout.personList;
        },
        add() {
            return this.$store.state.countAbout.sum;
        },
        firstPersonName() {
            return this.$store.getters['personAbout/firstPersonName'];
        }
    },
    methods: {
        addPerson() {
            const personObj = { id: nanoid(), name: this.name };
            this.$store.commit('personAbout/ADD_PERSON', personObj);
            this.name = '';  //添加完了输入框置空
        },
        addPersonHan() {
            const personObj = { id: nanoid(), name: this.name };
            this.$store.dispatch('personAbout/addPersonHan', personObj);
        },
        addPersonServer() {
            this.$store.dispatch('personAbout/addPersonServer');
        }
    },
};
</script>

注意:只有指定相应的模块名才能找到对应的数据和方法(前提是模块开启了命名空间)

Vue2(十二):Vuex_第5张图片

你可能感兴趣的:(Vue,vue.js,前端,javascript)