文章目录
vuex概念
vuex是专门在Vue中实现集中式
状态(数据
)管理的一个Vue插件
,对vue应用中
多个组件的共享状态(数据
)进行集中式的管理
(读/写)。
vuex也是一种组件间通信的方式
,且适用于任意组件间通信。
引入:
需求:组件B、C、D 都需要获取和修改
组件A上的数据X。
如果使用全局事件总线实现
:
这时候我们就会想既然所有组件都要使用组件A中的数据我们为什么不直接把组件A中的数据提取出来作为公共数据呢,这就引入了vuex。
vuex不属于任何一个组件,里面存储的是组件公用的数据,任何组件都可以获取和修改vuex中的数据:
vuex的使用情况
- 多个组件依赖于同—状态 (即多个组件需要使用同一个数据)
- 来自不同组件的行为需要变更同一状态 (即不同组件的函数需要修改同一个数据)
所以只要用到共享数据就可以使用vuex。
vuex的工作原理
工作原理讲解
-
如果vue组件的请求有任何的业务逻辑时也必须调用actions
-
联想:
可以将Vue Components看作顾客,Actions看作服务员,Mutations看作厨师来理解他们的关系。如果顾客不知道点什么菜(不知道参数
),可以呼叫服务员来确定点菜菜单,再交由后厨(vue组件需要dispatch给actions再commit给mutation
);如果顾客知道点什么菜(已知参数
)就无需服务员了,直接让后厨做即可(vue组件直接commit给mutation
);如果顾客知道点什么菜(已知参数
)但是菜的口味需要特殊标明(有业务逻辑
),还是需要通过服务员交由后厨。 -
在开发中,因为vue组件需要调用dispatch函数,而dispatch函数在store身上,所以需要设置使vc(组件实例对象)可以看见store。
实例
搭建vuex开发环境
搭建vuex开发环境需要四步:
- 安装 :
npm i vuex
- 配置:
Vue.use(Vuex)
- 设置store
- 使得vc可以看见store
安装
执行命令 npm i vuex
暗转的时候需要注意一个版本问题:
vue2中,要用vuex的3版本
vue3中,,要用vuex的4版本
配置
在main.js中输入:
import Vuex from 'vuex'
Vue.use(Vuex)
设置store、使得vc可以看见store
使vm,vc可以读取store属性
配置好vuex之后,就可以在创建vue实例对象的时候配置store配置项
,
new Vue({
el: "#root",
store:"hello",
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
})
配置完成之后,vm和vc身上就会多出一个$store属性
创建store,管理vuex
在src文件夹下创建store/index.js
文件:
(创建store一定要在Vue.use(Vuex)
之后,由于脚手架的文件执行顺序问题在main.js无法确保调用store/indxs.js在Vue.use(Vuex)
之后调用,所以将main.js的Vue.use(Vuex)
写在store/index.js
文件中)
// 该文件用于创建vuex中最为核心的store
// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 准备actions——用于相应组件中的动作
const actions= {}
//准备mutations—用于操作数据(state)
const mutations = {}
//准备state—用于存储数据
const state = {}
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
actions: actions,
mutations: mutations,
state: state
})
main.js
/**
* 该文件是整个项目的入口文件
*/
// 引入vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 引入插件
import vueResource from 'vue-resource'
// 引入store
import store from './store/index'
// 关闭vue生产提示
Vue.config.productionTip = false
Vue.use(vueResource)
// 创建vue实例对象
new Vue({
el: "#root",
store,
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
})
这样配置出的store就有我们想用的各种方法了。
脚手架加载文件顺序问题
vue的脚手架会先扫描文件寻找所有的import语句,并先执行该文件的所有import
语句。
Vuex的开发者工具
vuex的配置
vuex的初始化数据status配置
store/index.js
//准备state—用于存储数据
const state = {
sum:0,//初始数据
}
// 创建并暴露store
export default new Vuex.Store({
state: state,
})
vuex的actions配置
store/index.js
const actions = {
//action方法
jiaOdd(context, value) {
console.log("actions中的jiaOdd 被调用了")
if(context.state.sum%2)
context.commit('JIA',value)
},
jiaWait(context, value) {
console.log("actions中的jiaWait 被调用了")
setTimeout(() => {
context.commit('JIA',value)
},500)
}
}
// 创建并暴露store
export default new Vuex.Store({
actions: actions,
})
vuex的mutations配置
store/index.js
//准备mutations—用于操作数据(state)
const mutations = {
// mutations的操作数据
JIA(state, value) {
console.log("mutations中的JIA被调用了")
state.sum+=value
},
JIAN(state, value) {
console.log("mutations中的JIAN被调用了")
state.sum-=value
}
}
// 创建并暴露store
export default new Vuex.Store({
s mutations: mutations,
})
vuex的getters配置及使用
- 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
- 在store.js中追加 getters配置
store/index.js
// 准备getters—用于state数据加工
const getters = {
bigSum(state) {
return state.sum*10
}
}
export default new Vuex.Store({
getters:getters
})
- getters数据读取:
$store.getters.bigSum
求和案例
实现效果:
纯vue实现
Count.vue
<template>
<div>
<h1>当前求和为{{sum}}</h1>
<select v-model="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name: 'CountVue',
data() {
return {
n: 1,//用户选择的数据
sum:1,//当前的和
}
},
methods: {
increment() {
this.sum += this.n
},
decrement() {
this.sum -= this.n
},
incrementOdd() {
if (this.sum % 2) {
this.sum += this.n
}
},
incrementWait() {
setTimeout(() => {
this.sum += this.n
},500)
}
}
}
</script>
<style>
button{
margin: 5px;
}
</style>
app.vue
<template>
<div>
<Count></Count>
</div>
</template>
<script>
import Count from "@/components/Count";
export default {
name: "App",
components: {
Count
}
}
</script>
<style lang="css">
</style>
vuex实现
Count.vue
<template>
<div>
<h1>当前求和为{{$store.state.sum}}</h1>
<select v-model="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name: 'CountVue',
data() {
return {
n: 1,//用户选择的数据
}
},
methods: {
increment() {
this.$store.commit('JIA',this.n)
},
decrement() {
this.$store.commit('JIAN', this.n)
},
incrementOdd() {
this.$store.dispatch('jiaOdd', this.n)
},
incrementWait() {
this.$store.dispatch('jiaWait', this.n)
}
}
}
</script>
<style>
button{
margin: 5px;
}
</style>
App.vue
<template>
<div>
<h1>当前求和为{{$store.state.sum}}</h1>
<select v-model="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name: 'CountVue',
data() {
return {
n: 1,//用户选择的数据
}
},
methods: {
increment() {
this.$store.commit('JIA',this.n)
},
decrement() {
this.$store.commit('JIAN', this.n)
},
incrementOdd() {
this.$store.dispatch('jiaOdd', this.n)
},
incrementWait() {
this.$store.dispatch('jiaWait', this.n)
}
}
}
</script>
<style>
button{
margin: 5px;
}
</style>
store/index.js
// 该文件用于创建vuex中最为核心的store
// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 准备actions——用于相应组件中的动作
const actions = {
// jia: function () {} 简写为
// jia(context, value) {
// console.log("actions中的jia被调用了")
// // context:将store中我们可能用到的方法包中成函数放到context中
// // value是传过来的数据
// context.commit('JIA',value)
// },
// jian(context, value) {
// console.log("actions中的jian 被调用了")
// context.commit('JIAN',value)
// },
jiaOdd(context, value) {
console.log("actions中的jiaOdd 被调用了")
if(context.state.sum%2)
context.commit('JIA',value)
},
jiaWait(context, value) {
console.log("actions中的jiaWait 被调用了")
setTimeout(() => {
context.commit('JIA',value)
},500)
}
}
//准备mutations—用于操作数据(state)
const mutations = {
JIA(state, value) {
console.log("mutations中的JIA被调用了")
state.sum+=value
},
JIAN(state, value) {
console.log("mutations中的JIAN被调用了")
state.sum-=value
}
}
//准备state—用于存储数据
const state = {
sum:0,//当前的和
}
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
actions: actions,
mutations: mutations,
state: state
})
组件中读取vuex中的数据:
$store.state.sum
组件中修改vuex中的数据:
$store.dispatch('action中的方法名’,数据)
或$store.commit( 'mutations中的方法名' ,数据)
四个map方法的使用
由于在vuex中取数据需要这样写 $store.state.变量名
,比较麻烦为了更简便的写法vuex为我们提供了map方法
mapState
两种写法:(在computed中编写)
- 对象写法:
...mapState({计算属性名 : 'state中变量的名' }),
- 数组写法:(前提是获取的计算属性名称和state中变量的名称一致)
...mapState(['state中变量的名' ]),
- 作用: 借助mapState生成计算属性,从State中读取数据
...
是ES6语法是说将mapState对象中的键值对直接放到中computed
eg:store/index.js:
const state = {
sum: 0,//当前的和
name: 'yang',
age: 18
}
组件获取并应用数据:
<template>
<div>
<h1>当前求和为{{sum}}</h1>
<h1>姓名:{{name}}</h1>
<h1>年龄:{{age}}</h1>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'CountVue',
computed: {
// 手动获取数据
/*sum(){
return this.$store.state.sum
},
name(){
return this.$store.state.name
},
age(){
return this.$store.state.age
}*/
// 借助mapState生成计算属性,从state中读取数据(对象写法)
// ...是ES6语法是说将mapState对象中的键值对直接放到中computed
...mapState({ sum: 'sum', name: 'name', age: 'age' }),
// 借助mapState生成计算属性,从state中读取数据(数组写法)
// 数组写法的前提是获取的计算属性名称和state中变量的名称一致
...mapState(['sum', 'name', 'age' ])
}
}
</script>
mapGetters
两种写法:(在computed中编写)
- 对象写法:
...mapGetters({计算属性名 : 'getter中函数的名' }),
- 数组写法:(前提是获取的计算属性名称和getter中函数的名称一致)
...mapGetters(['getter中函数的名' ]),
- 作用: 借助mapGetters生成计算属性,从Getters中读取数据
...
是ES6语法是说将mapGetters对象中的键值对直接放到中computed
eg:store/index.js:
// 准备getters—用于state数据加工
const getters = {
bigSum(state) {
return state.sum*10
}
}
组件获取并应用数据:
<template>
<div>
<h1>数据放大10倍{{bigSum}}</h1>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'CountVue',
computed: {
/*
// 手写
bigSum() {
return this.$store.getters.bigSum;
}
*/
// 对象写法
...mapGetters({bigSum:'bigSum'}),
// 数组写法
...mapGetters(['bigSum']),
}
}
</script>
mapActions
两种写法:(在methods中编写)
- 对象写法:
...mapActions({方法 : 'Actions中函数的名' }),
- 数组写法:(前提是获取的方法名称和Actions中的函数名称一致)
...mapActions(['Actions函数的名' ]),
- 作用:借助mapActions生成对应的方法,方法中会调用dispatch去联系actions
...
是ES6语法是说将mapActions对象中的键值对直接放到中methods
需要注意的是如果需要传递参数需要在组件模板使用方法的时候传递参数
eg:store/index.js:
const actions = {
incrementOdd(context, value) {
console.log("actions中的jiaOdd 被调用了")
if(context.state.sum%2)
context.commit('increment',value)
},
incrementWait(context, value) {
console.log("actions中的jiaWait 被调用了")
setTimeout(() => {
context.commit('increment',value)
},500)
}
}
组件获取并应用数据:
<template>
<div>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
name: 'CountVue',
data() {
return {
n: 1,//用户选择的数据
}
},
methods: {
// 手动写
/*
incrementOdd() {
this.$store.dispatch('jiaOdd', this.n)
},
incrementWait() {
this.$store.dispatch('jiaWait', this.n)
}*/
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions({
incrementOdd: 'incrementOdd', incrementWait: 'incrementWait'
}),
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
...mapActions(['incrementOdd', 'incrementWait'])
},
}
</script>
mapMutations
两种写法:(在methods中编写)
- 对象写法:
...mapMutations({方法 : 'Mutations中函数的名' }),
- 数组写法:(前提是获取的方法名称和Mutations中的函数名称一致)
...mapMutations['Mutations函数的名' ]),
- 作用:借助mapMutations生成对应的方法,方法中会调用commit去联系mutations
...
是ES6语法是说将mapMutations对象中的键值对直接放到中methods
需要注意的是如果需要传递参数需要在组件模板使用方法的时候传递参数
eg:store/index.js:
//准备mutations—用于操作数据(state)
const mutations = {
increment(state, value) {
console.log("mutations中的JIA被调用了")
state.sum+=value
},
decrement(state, value) {
console.log("mutations中的JIAN被调用了")
state.sum-=value
}
}
组件获取并应用数据:
<template>
<div>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
name: 'CountVue',
data() {
return {
n: 1,//用户选择的数据
}
},
methods: {
// 手动写
/* increment() {
this.$store.commit('JIA',this.n)
},
decrement() {
this.$store.commit('JIAN', this.n)
},*/
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations({ increment: 'increment', decrement:'decrement'}),
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
...mapMutations(['increment', 'decrement']),
},
}
</script>
mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
多组件数据共享数据
实现效果:
app.vue:
<template>
<div>
<Count></Count>
<hr/>
<Person/>
</div>
</template>
<script>
import Count from "@/components/Count";
import Person from "@/components/Person";
export default {
name: "App",
components: {
Count,
Person
},
mounted() {
console.log(this)
}
}
</script>
<style lang="css">
</style>
Count.vue
<template>
<div>
<h1>当前求和为{{sum}}</h1>
<h1>数据放大10倍{{bigSum}}</h1>
<select v-model="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
<h3 style="color:red">下方组件的总人数是:{{ personList.length }}</h3>
</div>
</template>
<script>
import { mapState,mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'CountVue',
data() {
return {
n: 1,//用户选择的数据
}
},
methods: {
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations({ increment: 'increment', decrement:'decrement'}),
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
...mapMutations(['increment', 'decrement']),
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions({
incrementOdd: 'incrementOdd', incrementWait: 'incrementWait'
}),
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
...mapActions(['incrementOdd', 'incrementWait'])
},
computed: {
// 借助mapState生成计算属性,从state中读取数据(对象写法)
...mapState({ sum: 'sum', name: 'name', age: 'age', personList :'personList'}),
// 借助mapState生成计算属性,从state中读取数据(数组写法)
...mapState(['sum', 'name', 'age','personList']),
/*************************************************************** */
// 借助mapGetters生成计算属性,从Getters中读取数据(对象写法)
...mapGetters({bigSum:'bigSum'}),
// 借助mapGetters生成计算属性,从Getters中读取数据(数组写法)
...mapGetters(['bigSum']),
}
}
</script>
<style>
button{
margin: 5px;
}
</style>
Person.vue
<template>
<div>
<h1>人员列表</h1>
请输入人员名字:<input v-model="name" />
<button @click="personAdd">添加</button>
<ul>
<li v-for="person in personList" :key="person.id">{{ person.name }}</li>
</ul>
<h3 style="color:red">上方组件的sum是:{{sum }}</h3>
</div>
</template>
<script>
import { nanoid } from "nanoid"
export default {
name: 'PersonVue',
data() {
return {
name:''
}
},
computed:{
personList(){
return this.$store.state.personList
},
sum() {
return this.$store.state.sum
}
},
methods:{
personAdd(){
const personObj = { id: nanoid(), name: this.name }
this.$store.commit('personAdd', personObj)
this.name=''
}
}
}
</script>
src/store/index.js
// 该文件用于创建vuex中最为核心的store
// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 准备actions——用于相应组件中的动作
const actions = {
incrementOdd(context, value) {
console.log("actions中的jiaOdd 被调用了")
if(context.state.sum%2)
context.commit('increment',value)
},
incrementWait(context, value) {
console.log("actions中的jiaWait 被调用了")
setTimeout(() => {
context.commit('increment',value)
},500)
}
}
//准备mutations—用于操作数据(state)
const mutations = {
increment(state, value) {
console.log("mutations中的JIA被调用了")
state.sum+=value
},
decrement(state, value) {
console.log("mutations中的JIAN被调用了")
state.sum-=value
},
personAdd(state, value) {
console.log("mutations中的personAdd被调用了")
state.personList.unshift(value)
}
}
//准备state—用于存储数据
const state = {
sum: 0,//当前的和
personList: [{
id:'001',name:'张三'
}]
}
// 准备getters—用于state数据加工
const getters = {
bigSum(state) {
return state.sum*10
}
}
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
actions: actions,
mutations: mutations,
state: state,
getters:getters
})
vuex模块化
在vuex的index.js文件中最好将服务于不同组件的数据放到不同的模块中
src/store/index.js文件模块化写法
所以上述的src/store/index.js文件可以调整为:
定义countOptions
和personOptions
对象分别配置两个组件的vuex
// 该文件用于创建vuex中最为核心的store
// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
const countOptions = {
namespaced:true,//设置namespaced为true,为了让组件中的map方法能够识别该配置
actions: {
incrementOdd(context, value) {
console.log("actions中的jiaOdd 被调用了")
if(context.state.sum%2)
context.commit('increment',value)
},
incrementWait(context, value) {
console.log("actions中的jiaWait 被调用了")
setTimeout(() => {
context.commit('increment',value)
},500)
}
},
mutations: {
increment(state, value) {
console.log("mutations中的JIA被调用了")
state.sum+=value
},
decrement(state, value) {
console.log("mutations中的JIAN被调用了")
state.sum-=value
},
},
state: {
sum: 0,//当前的和
},
getters: {
bigSum(state) {
return state.sum*10
}
}
}
const personOptions = {
namespaced:true,//设置namespaced为true,为了让组件中的map方法能够识别该配置
actions: {},
mutations: {
personAdd(state, value) {
console.log("mutations中的personAdd被调用了")
state.personList.unshift(value)
}
},
state: {
personList: [{
id:'001',name:'张三'
}]
},
getters:{}
}
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
modules: {
countOptions: countOptions,
personOptions:personOptions,
}
})
- 此时的
$store.state
:
- 每个配置中需要写设置
namespaced:true
,为了让组件中的map方法能够识别该配置。
组件使用map方法获取store上的变量
使用组件化的写法,那么原来获取变量的方法就不适用了。以mapState为例
...mapState(['sum', 'name', 'age','personList']),
mapState获取的是state中有的数据,可是按如上模块化写法$store.state中只有countOptions
和personOptions
,而变量的具体值在countOptions
和personOptions
中, 所以不能按原来的方法写。
方法一:接收countOptions
和personOptions
eg:Count.vue
<template>
<div>
<h1>当前求和为{{countOptions.sum}}</h1>
<h3 style="color:red">下方组件的总人数是:{{ personOptions.personList.length }}</h3>
</div>
</template>
<script>
import { mapState,mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'CountVue',
computed: {
// 借助mapState生成计算属性,从state中读取数据(数组写法)
...mapState(['countOptions','personOptions']),
},
}
</script>
方法二:使用namespaced:true
当在vuex中配是的时候,如果添加namespaced:true
配置,就可以通过参数直接获取该组件的配置。
格式:...mapState('组件配置名', ['变量']),
eg:Count.vue
<template>
<div>
<h1>当前求和为{{sum}}</h1>
<h3 style="color:red">下方组件的总人数是:{{personList.length }}</h3>
</div>
</template>
<script>
import { mapState,mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'CountVue',
computed: {
// 借助mapState生成计算属性,从state中读取数据(数组写法)
...mapState('countOptions', ['sum']),
...mapState('personOptions', ['personList']),
},
}
</script>
其他的 mapGetters, mapMutations, mapActions
也可以使用该方法获取值。
组件不使用map方法获取store上的变量
- 传统的获取state值得方法:
return this.$store.state.personList
模块化后就应该写成:return this.$store.state.personOptions.personList
- 传统的获取getters值得方法:
return this.$store.firstPersonName
模块化后就应该写成:return this.$store.getters['personOptions/firstPersonName']
- 传统的获取actions值得方法:
this.$store.dispatch('addPersonWang', personObj)
模块化后就应该写成:this.$store.dispatch('personOptions/addPersonWang', personObj)
- 传统的获取mutations值得方法:
this.$store.commit('personAdd', personObj)
模块化后就应该写成:this.$store.commit('personOptions/personAdd', personObj)
即前面都需要添加组件的配置名。
eg:Person.vue
<template>
<div>
<h1>人员列表</h1>
请输入人员名字:<input v-model="name" />
<button @click="personAdd">添加</button>
<button @click="addPersonWang">添加一个姓王的人</button>
<ul>
<li v-for="person in personList" :key="person.id">{{ person.name }}</li>
</ul>
<h3>列表中第一个人的名字是:{{firstPersonName}}</h3>
<h3 style="color:red">上方组件的sum是:{{sum }}</h3>
</div>
</template>
<script>
import { nanoid } from "nanoid"
export default {
name: 'PersonVue',
data() {
return {
name:''
}
},
computed:{
personList(){
return this.$store.state.personOptions.personList
},
sum() {
return this.$store.state.countOptions.sum
},
firstPersonName() {
return this.$store.getters[`personOptions/firstPersonName`]
}
},
methods:{
personAdd(){
const personObj = { id: nanoid(), name: this.name }
this.$store.commit('personOptions/personAdd', personObj)
this.name=''
},
addPersonWang() {
const personObj = { id: nanoid(), name: this.name }
this.$store.dispatch('personOptions/addPersonWang', personObj)
this.name = ''
}
}
}
</script>
将src/store/index.js拆分成不同的文件
拆分成index.js、count.js、person.js
index.js:
// 该文件用于创建vuex中最为核心的store
// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
modules: {
countOptions: countOptions,
personOptions:personOptions,
}
})
count.js:
export default {
namespaced:true,//设置namespaced为true,为了让组件中的map方法能够识别该配置
actions: {
incrementOdd(context, value) {
console.log("actions中的jiaOdd 被调用了")
if(context.state.sum%2)
context.commit('increment',value)
},
incrementWait(context, value) {
console.log("actions中的jiaWait 被调用了")
setTimeout(() => {
context.commit('increment',value)
},500)
}
},
mutations: {
increment(state, value) {
console.log("mutations中的JIA被调用了")
state.sum+=value
},
decrement(state, value) {
console.log("mutations中的JIAN被调用了")
state.sum-=value
},
},
state: {
sum: 0,//当前的和
},
getters: {
bigSum(state) {
return state.sum*10
}
}
}
person.js:
export default {
namespaced:true,//设置namespaced为true,为了让组件中的map方法能够识别该配置
actions: {
// 只添加姓王的人
addPersonWang(context, value) {
if (value.name.indexOf('王') === 0) {
context.commit('personAdd',value)
} else {
alert('添加的人不姓王')
}
}
},
mutations: {
personAdd(state, value) {
console.log("mutations中的personAdd被调用了")
state.personList.unshift(value)
}
},
state: {
personList: [{
id:'001',name:'张三'
}]
},
getters: {
firstPersonName(state) {
return state.personList[0].name
}
}
}
模块化的优点
让代码更好维护,让多种数据分类更加明确。
vuex的响应式总结
再vue的data中定义的数据是响应式的,即数据改变就会重新渲染页面。
那vuex中获取的数据如何实现响应式,有两种方式可以实现响应式:
- 直接在页面使用
$store.state.变量名
获取数据,这样当store中的数据发生改变,就会反映到$store.state.变量名
,进而重新渲染页面 - 在compute中使用
...mapState({计算属性名 : 'state中变量的名' })
获取数据,这样当store中的数据发生改变,利用compute的特性,compute中对应的属性值也会发生变化,就会重新渲染页面
注意,不能在data中获取store的值,再将该值渲染到页面中,这样是不能实现响应式的。因为data没有compute的特性(即当它所依赖的值发生变化就会重新计算),所以当store中的值发生变化时,data对应的值并不会发生变化,就不会引起响应式。
即:
这样的代码
data() {
return {
tableData: this.$store.state.tableData
};
}
然后在 template 中使用 tableData
<el-table :data="tableData" class="tablePst">
<el-table-column label="登录名" prop="loginname"></el-table-column>
<el-table-column label="真实姓名" prop="realname"></el-table-column>
</el-table>
这样的代码,当store中的值发生变化的时候,页面是不会重新渲染进行更新的。
使用actions向服务器发送请求
eg:
actions: {
addPersonServer(context) {
axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
response => {
context.commit('personAdd',{id:nanoid(),name:response.data})
},
error =>{
console.log(error.message)
}
)
}
},
Vue3使用vuex —— vuex4.0
引入改变
vue3中使用app.use()
引入,main.js:
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'
const app = createApp(App)
app.use(store)
app.mount('#app')
store创建改变
使用createStore()
创建store。
store.js
import { createStore } from 'vuex'
export const store = createStore({
state () {
return {
count: 1
}
},
mutations() {
return {}
},
actions() {
return {}
},
getters() {
return {}
}
})
或者
import { createStore } from 'vuex'
const state = {}
const mutations = {
// mutations的操作数据
JIA(state, value) {
},
JIAN(state, value) {
}
}
const actions= {}
const getters= {}
export const store = createStore({
state,
mutations,
actions,
getters
})
store读取修改
在组件的 setup 钩子函数中使用 useStore
组合式函数来访问 store。等同于选项式API的this.$store
。
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
}
}
读取:
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
return {
// 在 computed 函数中访问 state
count: computed(() => store.state.count),
// 在 computed 函数中访问 getter
double: computed(() => store.getters.double),
// 使用 mutation
increment: () => store.commit('increment'),
// 使用 action
asyncIncrement: () => store.dispatch('asyncIncrement')
}
}
}
当然 mapState、mapGetters、mapActions、mapMutations 这些方法都是适用的。
TypeScript支持
Vuex 4 删除了this.$store
在 Vue 组件中的全局类型声明
。当使用 TypeScript 时,必须声明自己的模块补充:
vuex-shim.d.ts
import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'
declare module '@vue/runtime-core' {
// 声明自己的 store state
interface State {
count: number
}
interface ComponentCustomProperties {
$store: Store<State>
}
}
如果项目使用了ts,则需要将上述代码放置在项目中允许this.$store
被正确地使用。
高级使用
插件
Vuex 的 store 接受 plugins
选项,用来扩展vuex地功能
插件定义
const store = createStore({
// ...
plugins: [myPlugin]
})
myPlugin是自定义的插件方法,Vuex 插件就是一个函数,它接收 store 作为唯一参数:
function createWebSocketPlugin (socket) {
return (store) => {
socket.on('data', data => {
store.commit('receiveData', data)
})
store.subscribe(mutation => {
if (mutation.type === 'UPDATE_DATA') {
socket.emit('update', mutation.payload)
}
})
}
}
const plugin = createWebSocketPlugin(socket)
内置插件
Vuex自带一个内置插件用于一般的调试,createLogger 插件仅在开发环境使用。
import { createLogger } from 'vuex'
const store = createStore({
plugins: [createLogger()]
})
createLogger 有如下配置项:
const logger = createLogger({
collapsed: false, // 自动展开记录的 mutation
filter (mutation, stateBefore, stateAfter) {
// 若 mutation 需要被记录,就让它返回 true 即可
// 顺便,`mutation` 是个 { type, payload } 对象
return mutation.type !== "aBlocklistedMutation"
},
actionFilter (action, state) {
// 和 `filter` 一样,但是是针对 action 的
// `action` 的格式是 `{ type, payload }`
return action.type !== "aBlocklistedAction"
},
transformer (state) {
// 在开始记录之前转换状态
// 例如,只返回指定的子树
return state.subTree
},
mutationTransformer (mutation) {
// mutation 按照 { type, payload } 格式记录
// 我们可以按任意方式格式化
return mutation.type
},
actionTransformer (action) {
// 和 `mutationTransformer` 一样,但是是针对 action 的
return action.type
},
logActions: true, // 记录 action 日志
logMutations: true, // 记录 mutation 日志
logger: console, // 自定义 console 实现,默认为 `console`
})
严格模式
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的
,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
开启严格模式,只需要在createStore中配置strict为true
即可:
const store = createStore({
// ...
strict: true
})
不要在生产环境下启用严格模式,严格模式会深度检测状态树来检测不合规的状态变更,所以我们一般在生产环境下关闭严格模式以避免性能损失。
const store = createStore({
strict: process.env.NODE_ENV !== 'production'
})
vuex数据在表单中的使用
如果不是严格模式可以直接使用vuex的数据进行双向绑定:
<template>
<input v-model="obj.message">
</template>
<script>
//...
computed: {
...mapState({
message: state => state.obj.message
})
}
</script>
在严格模式下vuex的数据是不能直接修改的,需要通过调用mutation 实现。
<template>
<input :value="message" @input="updateMessage">
</template>
<script>
// ...
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
</script>
这样做有些许啰嗦,可以稍微封装一下:
<template>
<input v-model="message">
</template>
<script>
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
</script>
热重载
热重载是指数据更新的时候无需整个页面更新,只有被更新的模块会被替换
,而应用状态则会被保留。
使用 webpack 的模块热替换API,Vuex 支持在开发过程中热重载 mutation、module、action 和 getter。
webpack配置
热模块替换(或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块
,而无需完全刷新。HMR不适用于生产,这意味着它只能用于开发。
从webpack-dev-serverv4.0.0 开始,默认
启用模块热替换
。
如果是低于4.0的版本需要手动配置:
webpack.config.js
module.exports = {
devServer: {
hot: true,
},
}
vuex配置
对于 mutation 和模块,你需要使用 store.hotUpdate()
方法:
// store.js
import { createStore } from 'vuex'
import mutations from './mutations'
import moduleA from './modules/a'
const state = { ... }
const store = createStore({
state,
mutations,
modules: {
a: moduleA
}
})
if (module.hot) {
// 使 action 和 mutation 成为可热重载模块
module.hot.accept(['./mutations', './modules/a'], () => {
// 获取更新后的模块
// 因为 babel 6 的模块编译格式问题,这里需要加上 `.default`
const newMutations = require('./mutations').default
const newModuleA = require('./modules/a').default
// 加载新模块
store.hotUpdate({
mutations: newMutations,
modules: {
a: newModuleA
}
})
})
}