说在前面
- 什么是vuex,在vue中实现集中式状态(数据)管理的一个vue插件;对vue应用中多个组件的共享状态进行集中式的管理,适用于任意组件间通讯
- vuex在vue中使用时是在vue实例添加一个配置项叫store,并且所有的组件上有同样的配置(所有组件可以共享)
- store和vue实例一样需要new,所以Vuex的首字母大写,store来自Vuex.Store
- store里面的配置项有actions、mutations、state以及getters和modules
- 特别注意在创建store之前vue必须使用了插件即vue.use(vuex)
-
- 如果在main.js文件按照下面这样配置是不可以的
// main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//创建并引入store
import store from './store'
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
-
在创建store之前必须要应用vuex插件,但是构建工具的import语法执行时间是优先于当前文件的其他js代码的
-
这里的执行顺序是先执行import的文件再执行Vue.use(Vuex),所以是会报错的
-
正确的引入方式,main.js里面直接引入外部的store
// main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入store
import store from './store'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
- store的js
// store/index.js
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions——用于响应组件中的动作
const actions = {}
//准备mutations——用于操作数据(state)
const mutations = {}
//准备state——用于存储数据
const state = {}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
vuex的生命周期
从上图中可以看出:
- 绿色虚线中视vuex的三个重要组成部分(不是全部组成部分)Actions(一些行为)、Mutations(一些加工处理)、State对象(状态、数据),这三个组成部分需要store管理;
- 在组件中调用dispatch方法,两个参数,第一个参数是actions对象中的方法名,第二个参数是你想传递的参数
- actions对象中是一堆方法名,由组件调用dispatch触发,actions对象中的方法需要调用commit方法触mutations对象中的方法
- mutations对象中的方法就可以修改state里面的数据了,vuex会重新渲染所有引用了该数据的页面
总结:在上面的生命周期中actions是不是感觉有点多余,其实是可以在actions中处理异步数据(backend API是指后端接口);如果不需要异步请求,其实是可以直接在组件中调用commit直接和mutations对话的;vue的开发者调试工具(devtools)只能看到mutations中的数据
state
- 一些需要组件公用的数据
- 使用vuex后,store.state.xxx即可
actions
- 是一个对象
- 在组件中使用store.dispatch方法触发action是中的方法(一个action)
- 里面是一堆方法,接收两个参数,一个是上下文(Store中部分属性),一个是组件传过来的参数
- 通过context.commit方法提交一个mutation
- actions中的this指向Store
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex)
export default new Vuex.Store({
actions: {
add(context,value){
console.log(context,value)
console.log(this)
setTimeout(() => {
this.commit('JIA',value)
}, 2000);
}
},
mutations: {
JIA(state,value){
state.sum += value
},
JIA1(state,value){
state.sum += value
},
JIA2(state,value){
setTimeout(() => {
state.sum += value
}, 2000);
}
},
state: {
sum:0
}
})
VueCompoent
<template>
<div class="hello">
<h1>{{ msg }}</h1>
{{$store.state.sum}}
<button @click="add">actions中异步</button>
<button @click="add1">组件中异步</button>
<button @click="add2">mutations中异步</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data () {
return {
n:11
}
},
mounted () {
console.log(this)
},
methods: {
add(){
this.$store.dispatch('add', this.n)
},
add1(){
setTimeout(() => {
this.$store.commit('JIA1', this.n)
}, 2000);
},
add2(){
setTimeout(() => {
this.$store.commit('JIA2', this.n)
}, 2000);
}
}
}
</script>
上图中可以看到:context上下文中包含部分Store中的属性,够在actions中使用了,所以在actions对象中调用mutations中的方法可以使用第一个参数context,当然也可以使用this
- 在actions中可以使用异步,其实在组件调用dispatch之前和在mutations使用异步都是可以的
- 由上面三种情况可以看出,我们在mutations中使用异步更新state的数据,开发者工具跟踪数据是有问题的,所以官方说mutation必须是同步,action 可以包含任意异步操作。
mutations
- 是一个对象,对象中一堆方法
- mutations是更改state中的数据的唯一方法
- 在组件或者actions中使用commit提交一个mutation(mutations中的一个方法)
- 调用commit方法时传递参数的方式有两种
-
- 第一种传两个参数,第一个参数字符串是mutations中的方法名,第二个可选参数任意值
- 第二种传一个对象参数,type为mutations中的方法名,对象中的其他参数可选
- 每个mutation接收两个参数,第一个参数是Store里面的state,用作修改;第二个参数是commit触发时传递的参数
-
- commit提交mutation时如果传递两个参数,那么第二个参数和mutation接收的第二个参数一样
- 如果以对象形式传递,这里的第二个参数为commit提交时传递的对象,包含type
- 通过actions中的例子,可以看出mutations中其实可以异步操作,但是官方不建议这样做,因为开发者工具监听不到
-
- 所以没有复杂的业务逻辑和异步操作,可以直接在组件中提交mutation
现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的
getters
- 相当于computed,当state中的数据需要加工处理后再使用时,可以使用getters加工
- 接受两个参数,第一个是state,用于读取state中的所有值;第二个是getters
- getters中没有this
- 组件中使用getters中的值使用$store.getters.xxx
- getters中可以以函数的方式返回,这样在调用时就得以方法的形式调用,这样也就可以传递参数了
从 Vue 3.0 开始,getter 的结果不再像计算属性一样会被缓存起来。这是一个已知的问题,将会在 3.2 版本中修复
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex)
export default new Vuex.Store({
actions: {
add(context,value){
console.log(context,value)
console.log(this)
setTimeout(() => {
this.commit({
type:'JIA',
value,
value2:2
})
}, 2000);
}
},
mutations: {
JIA(state,value){
state.sum += value.value
},
JIA1(state,value){
state.sum += value
},
JIA2(state,value){
setTimeout(() => {
state.sum += value
}, 2000);
}
},
getters: {
bigSum:(state,getters)=>(n)=>{
console.log(n)
console.log(getters)
return state.sum*10+n;
}
},
state: {
sum:0
}
})
VueCompoent
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h1>{{$store.state.sum}}</h1>
<h2>bigSum:{{$store.getters.bigSum(2)}}</h2>
<button @click="add">actions中异步</button>
<button @click="add1">组件中异步</button>
<button @click="add2">mutations中异步</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data () {
return {
n:11
}
},
mounted () {
console.log(this)
},
methods: {
add(){
this.$store.dispatch('add', this.n)
},
add1(){
setTimeout(() => {
this.$store.commit('JIA1', this.n)
}, 2000);
},
add2(){
setTimeout(() => {
this.$store.commit('JIA2', this.n)
}, 2000);
}
}
}
</script>
modules
- 让代码更好维护,让多种数据分类更加明确
- 每个模块拥有自己的state、actions、mutations和getters
- 模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象即当前模块的Store
- 模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
- 模块内部的 getter,根节点状态会作为第三个参数暴露出来
- 挂载到VueComponent上如下图;所以getters的使用和state的使用是不一样的;state属性的属性名为在store/index.js注册模块时的名称
- actions和mutations和之前一样,如果有重名表现为数组;那么提交一次commit会执行JIA数组中的所有方法
命名空间
- 上面的mutations提交时候执行了两次,说明模块内部的mutation仍然是注册在全局命名空间的——这样使得多个模块能够对同一个mutation作出响应;actions和getters也是如此;不同模块定义相同的getters还会导致错误
- 命名空降会让你的模块具有更高的封装度和复用性,开启命名空间后该模块所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
- state的使用方式不变,getters的使用方式为在前面加上注册时的名称加斜杠
- actions和mutations也有相应改变
-
- 组件中调用dispatch时调整为this.$store.dispatch('person/changeAge', this.n)
- actions中调用commit时不必调整,并且执行的是当前Store中的mutation
- 组件中调用commit时调整为this.$store.commit('person/JIA', this.n)
四个map方法
- 在组件中使用vuex中的state、getters、actions、mutations都要写一长串,所以map帮助我们映射这4个对象中的内容
- 既然要映射,那只能在组件的js中去接收,然后再在标签中去使用
- 使用mapState和mapGetters将state和getters映射到组件的computed属性中再去使用
-
- 首先引入import {mapState,mapGetters} from 'vuex'
- 然后利用mapState和mapGetters解构赋值到computed中,所有的内容直接挂载到当前的VueComputed上
- 使用mapActions和mapMutations将actions和mutations映射到methods中
-
- 映射的时候mapActions会帮助我们调用$store.dispatch(xxx)函数
- mapMutations会帮助我们调用$store.commit(xxx)函数
- 此时参数传递需要写到函数调用的地方
- 映射有两种写法,参数可以是对象或者数组;四个map方式都有对象和数组这两种方式
//借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
...mapGetters({changeName:'person/changeName'}),
//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters('person',['changeName']),
// 如果没有使用命名空间可以直接写(数组写法)
...mapGetters(['changeName'])