vuex你会了吗?

178 阅读7分钟

说在前面

  • 什么是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的生命周期

image.png

从上图中可以看出:

  1. 绿色虚线中视vuex的三个重要组成部分(不是全部组成部分)Actions(一些行为)、Mutations(一些加工处理)、State对象(状态、数据),这三个组成部分需要store管理;
  2. 在组件中调用dispatch方法,两个参数,第一个参数是actions对象中的方法名,第二个参数是你想传递的参数
  3. actions对象中是一堆方法名,由组件调用dispatch触发,actions对象中的方法需要调用commit方法触mutations对象中的方法
  4. mutations对象中的方法就可以修改state里面的数据了,vuex会重新渲染所有引用了该数据的页面

总结:在上面的生命周期中actions是不是感觉有点多余,其实是可以在actions中处理异步数据(backend API是指后端接口);如果不需要异步请求,其实是可以直接在组件中调用commit直接和mutations对话的;vue的开发者调试工具(devtools)只能看到mutations中的数据

state

  • 一些需要组件公用的数据

image.png  

  • 使用vuex后,store属性挂载在VueComponent实例中,所以在组件中使用是store属性挂载在VueComponent实例中,所以在组件中使用是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>

image.png

上图中可以看到:context上下文中包含部分Store中的属性,够在actions中使用了,所以在actions对象中调用mutations中的方法可以使用第一个参数context,当然也可以使用this

  • 在actions中可以使用异步,其实在组件调用dispatch之前和在mutations使用异步都是可以的

image.png   image.png

image.png

  • 由上面三种情况可以看出,我们在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>

image.png

 

modules

  • 让代码更好维护,让多种数据分类更加明确
  • 每个模块拥有自己的state、actions、mutations和getters
  • 模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象即当前模块的Store
  • 模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
  • 模块内部的 getter,根节点状态会作为第三个参数暴露出来

image.png

  • 挂载到VueComponent上如下图;所以getters的使用和state的使用是不一样的;state属性的属性名为在store/index.js注册模块时的名称

image.png

  • actions和mutations和之前一样,如果有重名表现为数组;那么提交一次commit会执行JIA数组中的所有方法

image.png

image.png

命名空间

  • 上面的mutations提交时候执行了两次,说明模块内部的mutation仍然是注册在全局命名空间的——这样使得多个模块能够对同一个mutation作出响应;actions和getters也是如此;不同模块定义相同的getters还会导致错误
  • 命名空降会让你的模块具有更高的封装度和复用性,开启命名空间后该模块所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
  • state的使用方式不变,getters的使用方式为在前面加上注册时的名称加斜杠

image.png

image.png

  • actions和mutations也有相应改变
    • 组件中调用dispatch时调整为this.$store.dispatch('person/changeAge', this.n)
    • actions中调用commit时不必调整,并且执行的是当前Store中的mutation
    • 组件中调用commit时调整为this.$store.commit('person/JIA', this.n)

image.png

image.png

四个map方法

  • 在组件中使用vuex中的state、getters、actions、mutations都要写一长串,所以map帮助我们映射这4个对象中的内容
  • 既然要映射,那只能在组件的js中去接收,然后再在标签中去使用
  • 使用mapState和mapGetters将state和getters映射到组件的computed属性中再去使用
    • 首先引入import {mapState,mapGetters} from 'vuex'
    • 然后利用mapState和mapGetters解构赋值到computed中,所有的内容直接挂载到当前的VueComputed上

image.png

image.png

  • 使用mapActions和mapMutations将actions和mutations映射到methods中
    • 映射的时候mapActions会帮助我们调用$store.dispatch(xxx)函数
    • mapMutations会帮助我们调用$store.commit(xxx)函数
    • 此时参数传递需要写到函数调用的地方

image.png

image.png

  • 映射有两种写法,参数可以是对象或者数组;四个map方式都有对象和数组这两种方式
//借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
...mapGetters({changeName:'person/changeName'}),


//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters('person',['changeName']),
// 如果没有使用命名空间可以直接写(数组写法)
...mapGetters(['changeName'])