vuex简介
vuex是个状态管理库;简单理解就是给多个组件提供共享的全局变量,这个全局的变量是个对象,对象中的属性都是多组件共享的;并且vuex有一系列的机制对这个全局对象进行状态的管理。
既然vuex提供的是全局共享对象,那么和普通的全局变量有什么区别?
普通全局变量只提供共享,但是不具有响应式;vuex提供的全局变量对象,除了供组件共享之外,并且具有响应式。
试想如果想让多个组件共享对象,那么只需要将某个对象挂载到Vue.prototype上就可以,但是为什么我们不这么做,因为只挂载是没有响应式的。由此可知vuex的实现应该是:将共享对象挂载到vue的根原型 + 并且将共享对象加入响应式系统。
vuex相当于全局单例大管家,将组件需要共享的属性放在vuex中进行统一管理
vuex适用场景
如果只是单纯的父子组件通信传递直接采用props就可以;vuex是针对比较中大型的项目,并且是复杂组件之间通信跨多层级,并且组件间关联性不大,但是又需要通信,vuex就比较适合这种场景。
如果只是单一组件之间通信,就引入vuex这种集中式的状态存储管理机制,反而影响项目质量
vuex使用
1. vuex属性state的使用
// /src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// 2. 创建对象
const store = new Vuex.Store({
state: {
counter: 10
},
});
// 3. 导出store对象
export default store
// main.js
import Vue from "vue";
import App from "./App.vue";
import store from "./store";
new Vue({
// 为了在 Vue 组件中访问 this.$store property,你需要为 Vue 实例提供创建好的 store。
// Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式“注入”该 store 的机制
store,
render: h => h(App)
}).$mount("#app");
<!-- HelloVuex组件 -->
<template>
<h1>{{ $store.state.counter }}</h1>
</template>
<script>
export default {
}
</script>
<style>
</style>
<!-- App组件 -->
<template>
<div id="app">
<p>----------app组件内容-------</p>
<h1>{{ $store.state.counter }}</h1>
<button @click="++$store.state.counter">+</button>
<button @click="--$store.state.counter">-</button>
<p>----------helloVuex组件内容-------</p>
<hello-vuex />
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex.vue";
export default {
name: "App",
components: {
HelloVuex
}
};
</script>
<style>
</style>
2. vuex属性Mutations的使用
mutations中可以做同步操作,结合devtools跟踪组件的commit提交记录
2.1 mutations基本使用
// /store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// 2. 创建对象
const store = new Vuex.Store({
state: {
counter: 10
},
mutations: {
// mutations中的方法默认接受state参数
increment (state) {
++state.counter
},
decrement (state) {
--state.counter
}
}
});
// 3. 导出store对象
export default store
<!-- App.vue -->
<template>
<div id="app">
<p>----------app组件内容-------</p>
<h1>{{ $store.state.counter }}</h1>
<button @click="addCount">+</button>
<button @click="subCount">-</button>
<p>----------helloVuex组件内容-------</p>
<hello-vuex />
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex.vue";
export default {
name: "App",
components: {
HelloVuex
},
methods: {
addCount () {
// 通过commit发送给Mutations,commit参数的名字就是Mutations中定义increment方法的名字
this.$store.commit('increment')
},
subCount () {
this.$store.commit('decrement')
}
}
};
</script>
<style>
</style>
devtools跟踪每次commit记录示意图:
2.2 mutations传参问题
语法:
mutations中的属性由两部分组成:
- 字符串事件类型(type)
- 针对字符串事件类型所对应的回调函数(handle)
- 该回调函数的第一个参数默认是
state - 该回调函数的其他参数用户可以传入,用户传入的参数被称为
payload 负载
mutations: { increment: function(state, arg) { // code },
increment:就是字符串type function:是increment这个type对应的handle handle第一个参数:默认是state handle的arg:是用户传入payload
// 代码示例1:
mutations: {
// count是组件传入payload
add (state, count){
state.counter += count
}
},
methods: {
addCount () {
// this.$store.commit('increment')
// 10就是传递到mutations中的add这个type多对应的回调函数的payload
this.$store.commit('add', 10)
},
}
2.3 mutations的commit提交风格
普通提交: 普通方式提交除了指定mutations中的type字符串之外,只能再额外接受一个参数,这个参数可以是任何类型。
// 普通提交
mutations: {
// 这里count是10,但是age是undefined
add (state, count, age){
state.counter += count
}
},
methods: {
addCount () {
// 普通提交方式,10就对应add的参数count,而111无法传递到age中
this.$store.commit('add', 10, 111)
},
}
特殊提交: 特殊提交,不管提交多少参数都只映射到payload这个对象中
// 特殊提交
mutations: {
// 特殊方式提交时,payload是一个对象,
// payload: {
// type: 'add',
// count: 10,
// age: 1111
// }
add (state, payload){
state.counter += payload.count
}
},
methods: {
addCount () {
// 特殊方式的提交,不管提交多少参数都会传递到mutations的add的payload对象
this.$store.commit({
type: 'add',
count: 10,
age: 1111
})
},
}
2.4 mutations的类型常量
当mutations中的type太多时,有时候我们在组件中,进行commit提交如果没有从mutations中copy字符串type,手敲
// 这里如果手敲 'increment' 很容易出错,而且除了错,控制台是不报的,这时候错误不容易发现
// 所以建议当mutations中的type太多时,我们采用常量的方式引入
this.$store.commit('increment')
// mutations-type.js
export const IN_CREMENT = 'increment'
export const DE_CREMENT = 'decrement'
// store/index.js
import {
IN_CREMENT,
DE_CREMENT
} from 'mutations-type.js'
mutations: {
[IN_CREMENT](state){
// code
},
[DE_CREMENT](state){
// code
},
}
// app.vue组件中使用
import {
IN_CREMENT,
DE_CREMENT
} from 'mutations-type.js'
this.$store.commit(IN_CREMENT)
3. vuex属性getters的使用
Getters属性类似于vue中的计算属性,将state中的属性进行一系列加工之后进行返回;比如这里外界需要获取的是state中counter属性的平方,那么就可以写到Getters中。
当多个组件都需要获取state中counter属性的平方时就可以写在getters中,如果只是某一个组件使用,则可以直接写在组件的computed中,不需要提到getters中。
3.1 getters基本使用
// /store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// 2. 创建对象
const store = new Vuex.Store({
state: {
counter: 10
},
mutations: {
// mutations中的方法默认接受state参数
increment (state) {
++state.counter
},
decrement (state) {
--state.counter
}
},
getters: {
// getters中的方法默认接受state参数
// 外面的组件可以通过 $store.getters.powerCounter 进行访问
powerCounter (state) {
return state.counter * state.counter
}
},
actions: {},
modules: {}
});
// 3. 导出store对象
export default store
3.2 getters传参问题
语法:
// 语法示例1:
// 参数1:默认是state
// 参数2:默认是getters对象本身,这样用于再使用getters对象调用getters对象内部的的其他属性
getters: {
xxx(参数1, 参数2){
// code
}
}
// 语法示例2:
// getters中的属性不能接受外界组件参数,如果外界组件想传参进getters的属性中,可以让getters中的属性返回闭包函数。
getters: {
xxx(参数1, 参数2){
return function(arg){
// code
}
}
}
代码示例:
// /store/index.js示例:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
students: [
{ 'name': '令狐冲', 'age': 300 },
{ 'name': '东方不败', 'age': 18 },
{ 'name': '任我行', 'age': 100 },
{ 'name': '鸠摩智', 'age': 200 },
]
},
getters: {
moreToAge (state) {
return state.students.filter(item => item.age > 18)
},
moreToAgeLength (state, getters) {
return getters.moreToAge.length
},
// 根据外部组件传递的age参数筛选获取学生信息,这里返回闭包函数
studentById (state) {
return function (age) {
return state.students.filter(item => item.age == age)
}
}
},
});
export default store
<!-- App.vue示例 -->
<template>
<div id="app">
<p>----------store的getters使用-------</p>
<p>学生信息:{{ $store.state.students }}</p>
<p>大于18岁:{{ $store.getters.moreToAge }}</p>
<p>大于18岁学生个数:{{ $store.getters.moreToAgeLength }}</p>
<!-- 注意这个studentById属性返回闭包函数接受age参数 -->
<p>等于18岁:{{ $store.getters.studentById(18) }}</p>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style>
</style>
4. vuex属性actions的使用
actions的使用类似于mutations,区别在于,异步操作放在actions中,其次actions中的属性方法对应的默认参数不是state,而是context,context是一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters;但是context不是store实例本身
4.1 actions基本使用
// store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
info: {
'up': 'upup',
'down': '笑书神侠倚碧鸳'
}
},
mutations: {
updataInfo (state) {
state.info.up = '飞雪连天射白鹿'
}
},
actions: {
actionUpdataInfo (context, arg) {
return new Promise((resolved, rejected) => {
// 使用setTimeout模拟异步调用
// commit之后相当于异步调用完成,使用resolved通知外部组件异步请求成功
// 这里关于promise的一个知识点,同时resolved和rejected,谁先就先返回谁,另外一个就不调用了。
setTimeout(() => {
context.commit('updataInfo')
resolved(111)
rejected(222) // 不执行
console.log(arg) // 执行:我是携带的信息
}, 1000);
})
}
},
});
export default store
<!-- App.vue -->
<template>
<div id="app">
<p>----------store的actions使用-------</p>
<p>info: {{ $store.state.info }}</p>
<button @click="updateInfo">修改</button>
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex.vue";
export default {
name: "App",
components: {
HelloVuex
},
methods: {
updateInfo () {
// 组件流向action通过dispatch方法
// 这个promise的返回针对actionUpdataInfo这个action的,所以在其他地方调用actionUpdataInfo时,也是promise返回
this.$store.dispatch('actionUpdataInfo', '我是携带的信息')
.then(res => console.log(res))
.catch(e => console.log(e))
}
}
};
</script>
<style>
</style>
4.2 actions的dispatch分发风格
actions的dispatch类似于mutations的commit提交风格
普通dispatch分发: 普通方式分发,除了dispatch指定ations中的type字符串之外,只能再额外传递一个参数,这个额外的参数可以是任何类型
// 见4.1 actions基本使用
特殊dispatch(带payload)分发:
// store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
info: {
'up': 'upup',
'down': 'downdown'
}
},
mutations: {
// mutations中的方法默认接受state参数
updataInfo (state, payload) {
// payload: {
// type:"updataInfo",
// obj: {
// down: "笑书神侠倚碧鸳"
// type: "actionUpdataInfo"
// up: "飞雪连天射白鹿"
// }
// }
console.log(payload);
state.info.up = payload.obj.up
state.info.down = payload.obj.down
}
},
actions: {
actionUpdataInfo (context, payload) {
return new Promise((resolved, rejected) => {
setTimeout(() => {
// payload:
// {
// type: 'actionUpdataInfo',
// up: '飞雪连天射白鹿',
// down: '笑书神侠倚碧鸳'
// }
console.log('payload====', payload)
// 这里也采用mutations的payload的提交
context.commit({
type: 'updataInfo',
obj: payload
})
resolved(111)
rejected(222)
}, 1000);
})
}
},
});
export default store
<!-- App.vue -->
<template>
<div id="app">
<p>----------store的actions使用-------</p>
<p>info: {{ $store.state.info }}</p>
<button @click="updateInfo">修改</button>
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex.vue";
export default {
name: "App",
components: {
HelloVuex
},
methods: {
updateInfo () {
// 组件流向action通过dispatch方法
// this.$store.dispatch('actionUpdataInfo', '我是携带的信息')
// .then(res => console.log(res))
// .catch(e => console.log(e))
// payload对象方式提交
this.$store.dispatch({
'type': 'actionUpdataInfo',
'up': '飞雪连天射白鹿',
'down': '笑书神侠倚碧鸳'
})
.then(res => console.log(res))
.catch(e => console.log(e))
},
}
};
</script>
<style>
</style>
4.3 组合actions(官网示例)
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
5. vuex属性modules的使用
vuex对state的管理保持单一状态树的原则,就是坚持一个全局的store便于管理,但是当应用变得非常复杂时state中的变量越来越多而且会变的臃肿,vuex在设计的时候也考虑到这个问题,所以在store中添加modules这个属性模块,当状态不利于维护时,可以在modules属性中再维护store,并且每个store模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
// 这里暂不展开具体操作
vuex多页面状态管理
1. vuex的state状态值修改问题
在App组件中直接修改store的state中的属性值++$store.state.counter,页面不会报错,并且正常执行,但是官方不推荐这么做,原因见vuex的状态管理图
<!-- App组件 -->
<template>
<div id="app">
<p>----------app组件内容-------</p>
<h1>{{ $store.state.counter }}</h1>
<button @click="++$store.state.counter">+</button>
<button @click="--$store.state.counter">-</button>
<p>----------helloVuex组件内容-------</p>
<hello-vuex />
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex.vue";
export default {
name: "App",
components: {
HelloVuex
}
};
</script>
<style>
</style>
2. vuex状态管理理论
问题:vue组件直接和state交互不报错,并且正常显示;那为什么官方不推荐直接vue组件直接和state交互呢?
试想一种场景A,B,C三个组件都对state的同一个count属性进行修改,万一谁改错了,那我们怎么知道这次错了是被哪一个组件修改的呢?浑水摸鱼,无从查证;所以vue给vuex提供了一个devtools的浏览器插件,这个插件的作用就是为了跟踪state状态修改的记录,这样不管A,B,C哪个组件需要修改state的count属性都需要各自提commit到Mutations模块,然后Mutations模块再对commit进行同步处理和devtools交互,最终将修改更新到State模块。
问题:既然了解了Mutations模块的作用,那么Action模块呢?
Actions模块的主要作用是进行异步操作,如果vue组件修改state状态需要进行异步操作,那么就需要Actions模块;Backend(后端)API调用。Actions将异步处理完的结果commit到Mutations中,Muations再进行同步操作;如果没有异步操作那么Actions模块也可以不要了;vue组件就直接跟Mutations模块交互了,这个官方也是允许的。
问题:为什么vuex要求mutations中的方法必须是同步方法?
主要原因是因为当我们使用devtools时,devtools可以帮助我们捕捉mutations中的每次一快照,如果是异步操作的话,devtools将不能很好的追踪这个异步操作什么时候会被完成。
其实在mutations中可以进行异步请求,并且不报错。只是我们人为规范为了借助devtools追踪状态,方便开发调试。
谈谈store中state的响应式
state的响应式和vue的data属性的响应式一模一样,没有声明在state中的属性不具有响应式
如果想使state中的属性具有响应式,通过$set或者直接用新的对象给旧对象赋值