🥔:山不向我走来,我便向它走去
更多Vue知识请点击——Vue.js
VUE2-Day11
- 理解Vuex
- 1、Vuex是什么
- 2、什么时候使用Vuex
- Vuex环境搭建
- 1、安装vuex
- 2、创建store文件
- 3、main.js引入store
- Vuex的工作原理
- 1、原理图
- 2、用案例解释工作原理
- 3、注意点
- Vuex配置项
- 1、getters配置项
- 2、mapstate与mapGetters
- 3、mapActions与mapMutations
- 多组件共享数据
- Vuex模块化+命名空间(项目常用)
- 1、模块化的概念及作用
- 2、使用方式
- 3、综合案例
理解Vuex
1、Vuex是什么
概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
- 我们之前使用全局事件总线实现多组件共享数据,看起来挺好的问题不大,但是组件一多的话就会变得很麻烦:
- 我们现在使用Vuex实现多组件共享数据:
2、什么时候使用Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
其实就是——共享
Vuex环境搭建
1、安装vuex
安装:npm i vuex@3
注意Vue2一定要安装vuex3,如果是vue3可以直接npm i vuex
安装的是vuex4,可以去package.json
文件里看下vue的版本是啥,千万别输错了,不然会陷入痛苦的报错……
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;
3、main.js引入store
JS执行的时候会把import提升到顶部,与摆放顺序无关,如果放在main.js里 import 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
了
Vuex的工作原理
1、原理图
这个工作流程用一个通俗的例子来解释:VC就是顾客,actions就是服务员,mutations就是厨师,state就是菜的原料。顾客(VC)来到店里,先告诉(dispatch)服务员(actions)要吃啥菜,然后服务员把菜单给(commit)厨师(mutations),完了之后厨师对菜的原料(state)来个加工(mutate),最后再给(render)顾客(VC)。
2、用案例解释工作原理
使用vuex实现求和案例:
下拉框能选择要操作的数字,分别用总数对这个数字进行加减等操作。
下面的操作不懂的时候可以翻到前面的原理图,结合图更容易理解哦~
1、首先数据应该放在state里面,所以把求和数据给 vuex的state对象
const state = {
sum: 0, //初始化数据
};
2、页面上插值语法就该用
<h1>当前求和为:{{ $store.state.sum }}</h1>
3、在组件中的回调就可以用dispatch
发给actions
再commit
给mutations
。注意如果没有业务逻辑,直接commit
可以跳过actions
直接给mutations
(也就是上面原理图粉红色那条线)。
- 此处加和减都没啥业务逻辑,直接
跳过actions
然后commit给mutations
。 - 而奇数加和等一等再加存在业务逻辑,不能直接“加工”,所以要先用
dispatch发给actions
再commit给mutations
代码文件如下:
- Count.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',
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>
<style>
button {
margin-left: 5px;
}
</style>
- index.js
//该文件用于创建vuex中最核心的store
// 引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions:用于响应组件中的动作
const actions = {
//是奇数就加
//第一个参数是浓缩版的$store,方便你在这里调用commit把东西给mutations
//第二个参数是传过来的数据
oddAdd(context, value) {
// console.log('actions中的oddadd被调用了')
if (context.state.sum % 2) {
context.commit('JIA', value)
}
},
//等一等再加
waitAdd(context, value) {
// console.log('actions中的waitadd被调用了')
setTimeout(() => {
context.commit('JIA', value)
}, 1000)
},
}
//准备mutations:用于操作数据(state)
const mutations = {
//加
//第一个参数是state对象,第二个参数是传过来的数据
JIA(state, value) {
// console.log('mutations中的JIA被调用了', state, value)
state.sum += value
},
//减
JIAN(state, value) {
// console.log('mutations中的JIAN被调用了', state, value)
state.sum -= value
},
}
//准备state:用于存储数据
const state = {
sum: 0, //初始化数据,当前的和
}
//创建store并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
3、注意点
- 一般来说都会把网络请求或其他业务逻辑写到actions里面
- 其实actions里面也可以操作数据,但是如果不在mutations里操作数据,而在actions里操作数据,vuex开发者工具会失效的
- 组件中也可以越过actions,即不写dispatch,直接编写commit把数据传给mutations
Vuex配置项
1、getters配置项
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
<h1>当前求和放大十倍后为:{{ $store.getters.bigSum }}</h1>
4、其实state
就类似于data
,getters
就类似computed
2、mapstate与mapGetters
我们之前要往页面上放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>
真的是非常麻烦啊,想简单点写,写成下面这样:
<h1>当前求和为:{{ sum }}</h1>
<h1>当前求和放大十倍后为:{{ bigSum }}</h1>
<h1>我在{{ school }}学习{{ 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;
}
},
但是实际上亲自写计算属性来实现state插值语法computed这些东西复用性很差,vuex给我们提供了一个mapState
和mapGetters
方法:用于帮助我们把state
和getters
中的数据映射为计算属性。
- 用mapState和mapGetters方法
先引入这两个map方法: import {mapState,mapGetters} from 'vuex'
mapstate
与mapGetters
用到computed
里。
然后写法如下:
computed: {
//截取mapState生成计算属性,从state读取数据。(对象写法)
...mapState({ sum: 'sum', school: 'school', subject: 'subject' }),
//截取mapState生成计算属性,从state读取数据。(数组写法:生成的计算属性名和读取的属性名一样才能用)
// ...mapState([sum, school, subject]),
//截取mapGetters生成计算属性,从getters读取数据。(对象写法)
...mapGetters({ bigSum: 'bigSum' }),
//截取mapGetters生成计算属性,从getters读取数据。(数组写法:生成的计算属性名和读取的属性名一样才能用)
// ...mapGetters([bigSum]),
},
注意:对象写法可以任意起名,键对应计算属性方法名
,值对应state中的数据名
,如果方法名和数据名一样
,就可以用数组形式简写
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);
},
},
Vuex给我们提供了mapMutations
和mapActions
方法
mapMutations
方法:用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数。
mapActions
方法:用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数。
先引入这两个map方法: import {mapMutations,mapActions} from 'vuex'
mapMutations
与mapActions
用到methods
里。
写法如下:
methods: {
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations({ add: 'JIA', subtract: 'JIAN' }),
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
// ...mapMutations(['JIA','JIAN']),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions({ oddAdd: 'oddAdd', waitAdd: 'waitAdd' }),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
// ...mapMutations(['oddAdd','waitAdd']),
},
注意:mapActions
与mapMutations
使用时,其实创建的程序员费老大劲写的代码是不一样的,这两个玩意儿创建的东西是不带数据的,它创建的是这个:
add(value) {
this.$store.commit('JIA', value);
},
若需要传递参数value
,需要:在模板中绑定事件时传递好参数,否则参数value
是事件对象。
<button @click="JIA(addnum)">+</button>
<button @click="JIAN(addnum)">-</button>
<button @click="oddAdd(addnum)">当前求和为奇数再加</button>
<button @click="waitAdd(addnum)">等1秒再加</button>
注意:对象写法中,键是方法名
,值是传给mutations
或者actions
的方法名
,如果方法名和传的方法名一样可以简写为数组形式
多组件共享数据
- index.js
//该文件用于创建vuex中最核心的store
// 引入Vue
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions:用于响应组件中的动作
const actions = {
//是奇数就加
oddAdd(context, value) {
// console.log('actions中的oddadd被调用了')
if (context.state.sum % 2) {
context.commit('JIA', value)
}
},
//等一等再加
waitAdd(context, value) {
// console.log('actions中的waitadd被调用了')
setTimeout(() => {
context.commit('JIA', value)
}, 1000)
},
}
//准备mutations:用于操作数据(state)
const mutations = {
//加
JIA(state, value) {
// console.log('mutations中的JIA被调用了', state, value)
state.sum += value
},
//减
JIAN(state, value) {
// console.log('mutations中的JIAN被调用了', state, value)
state.sum -= value
},
ADD_PERSON(state, value) {
state.personList.unshift(value)
},
}
//准备state:用于存储数据
const state = {
sum: 0, //初始化数据,当前的和
school: 'potato',
subject: '前端',
personList: [{ id: '001', name: '张三' }],
}
//准备getters:用于将state中的数据进行加工
const getters = {
bigSum(state) {
return state.sum * 10
},
}
//创建store并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
getters,
})
- Count.vue
<template>
<div>
<h1>当前求和为:{{ sum }}</h1>
<h3>当前求和放大10倍:{{ bigSum }}</h3>
<h3>我在{{ school }}学习{{ subject }}</h3>
<h3 style="color: red">下方组件的总人数是:{{ personList.length }}</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="add(addnum)">+</button>
<button @click="subtract(addnum)">-</button>
<button @click="oddAdd(addnum)">当前求和为奇数再加</button>
<button @click="waitAdd(addnum)">等1秒再加</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'Count',
computed: {
//截取mapState生成计算属性,从state读取数据。(对象写法)
...mapState({
sum: 'sum',
school: 'school',
subject: 'subject',
personList: 'personList',
}),
//截取mapState生成计算属性,从state读取数据。(数组写法:生成的计算属性名和读取的属性名一样才能用)
// ...mapState([sum, school, subject]),
//截取mapGetters生成计算属性,从getters读取数据。(对象写法)
...mapGetters({ bigSum: 'bigSum' }),
//截取mapGetters生成计算属性,从getters读取数据。(数组写法:生成的计算属性名和读取的属性名一样才能用)
// ...mapGetters([bigSum]),
},
methods: {
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations({ add: 'JIA', subtract: 'JIAN' }),
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
// ...mapMutations(['JIA','JIAN']),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions({ oddAdd: 'oddAdd', waitAdd: 'waitAdd' }),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
// ...mapMutations(['oddAdd','waitAdd']),
},
}
</script>
<style>
button {
margin-left: 5px;
}
</style>
- Person.vue
<template>
<div>
<h1>人员列表</h1>
<input type="text" placeholder="请输入名字" v-model="name" />
<button @click="add">添加</button>
<ul>
<li v-for="p in personList" :key="p.id">{{ p.name }}</li>
</ul>
</div>
</template>
<script>
import { nanoid } from 'nanoid'
import { mapState } from 'vuex'
export default {
name: 'Person',
data() {
return {
name: '',
}
},
computed: {
...mapState(['personList']),
},
methods: {
add() {
const personObj = { id: nanoid(), name: this.name }
this.$store.commit('ADD_PERSON', personObj)
this.name = ''
},
},
}
</script>
<style></style>
Vuex模块化+命名空间(项目常用)
1、模块化的概念及作用
如果我们写的state,actions什么的是服务于多个种类的,比如有管加法的,有管人员的,这样放到一起很乱,所以可以把它们拆开
作用:让代码更好维护,让多种数据分类更加明确。
2、使用方式
可以都写到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'}),
3、综合案例
相信大家看完都有点懵了,可以直接看这个综合案例,几乎囊括了上文的所有知识点。
此案例的Count.vue和Person.vue里的计算属性和方法分别采用了两种不同的写法,Count.vue引入了mapXXX方法,Person.vue没有,可以对比一下他们的区别,你就知道mapXXX有多香。
建议大家复制下来自己运行看看,然后尝试自己凭印象再写一次。
案例目录结构如下:
- App.vue
<template>
<div>
<Count />
<hr />
<Person />
</div>
</template>
<script>
import Count from './components/Count.vue'
import Person from './components/Person.vue'
export default {
name: 'App',
components: { Count, Person },
}
</script>
<style lang="css"></style>
- main.js
// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
//引入插件
import vueResource from 'vue-resource'
//引入store
import store from './store'
// 关闭Vue的生产信息
Vue.config.productionTip = false
//使用插件
Vue.use(vueResource)
// 创建vm
new Vue({
el: '#app',
render: (h) => h(App),
store,
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
- 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并暴露store
export default new Vuex.Store({
modules: {
countAbout,
personAbout,
},
})
- Count.js
export default {
namespaced: true,
state: {
sum: 0, //初始化数据
school: 'potato',
subject: '前端',
},
getters: {
bigSum(state) {
return state.sum * 10
},
},
mutations: {
JIA(state, value) {
state.sum += value
},
JIAN(state, value) {
state.sum -= value
},
},
actions: {
oddAdd(context, value) {
context.commit('JIA', value)
},
waitAdd(context, value) {
setTimeout(() => {
context.commit('JIA', value)
}, 1000)
},
},
}
- Count.vue
<template>
<div>
<h1>当前求和为:{{ sum }}</h1>
<h3>当前求和放大10倍:{{ bigSum }}</h3>
<h3>我在{{ school }}学习{{ subject }}</h3>
<h3 style="color: red">下方组件的总人数是:{{ personList.length }}</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="add(addnum)">+</button>
<button @click="subtract(addnum)">-</button>
<button @click="oddAdd(addnum)">当前求和为奇数再加</button>
<button @click="waitAdd(addnum)">等1秒再加</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'Count',
data() {
return {
addnum: 1,
}
},
computed: {
//数组写法
...mapState('countAbout', ['sum', 'school', 'subject']),
//对象写法
...mapState('personAbout', { personList: 'personList' }),
...mapGetters('countAbout', ['bigSum']),
},
methods: {
...mapMutations('countAbout', { add: 'JIA', subtract: 'JIAN' }),
...mapActions('countAbout', ['oddAdd', 'waitAdd']),
},
}
</script>
<style>
button {
margin-left: 5px;
}
</style>
- Person.js
import axios from 'axios'
import { nanoid } from 'nanoid'
export default {
namespaced: true,
state: {
personList: [{ id: '001', name: '张三' }],
},
mutations: {
ADD_PERSON(state, value) {
state.personList.unshift(value)
},
},
actions: {
addPersonWu(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)
}
)
},
},
getters: {
firstPersonName(state) {
return state.personList[0].name
},
},
}
- Person.vue
<template>
<div>
<h1>人员列表</h1>
<input type="text" placeholder="请输入名字" v-model="name" />
<button @click="addPerson">添加</button>
<button @click="addPersonWu">添加一个姓吴的人</button>
<button @click="addPersonServer">借助服务器随机生成并添加一个名字</button>
<h2>第一个人的名字:{{ firstPersonName }}</h2>
<ul>
<li v-for="p in personList" :key="p.id">{{ p.name }}</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 = ''
},
addPersonWu() {
const personObj = { id: nanoid(), name: this.name }
this.$store.dispatch('personAbout/addPersonWu', personObj)
this.name = ''
},
addPersonServer() {
this.$store.dispatch('personAbout/addPersonServer')
this.name = ''
},
},
}
</script>
<style></style>