vue之vuex2.0使用详解

本文是我在学习vuex的时候看到的比较好的示例整理而成,需要看原文的可以看看文末参考部分的链接。

Vuex 的核心是 store, 它是一个通过 Vuex.Store 构造函数生成的对象。为什么它会是核心呢?因为我们调用这个构造函数创建store 对象的时候,给它传递参数中包装了state, mutation , action 等核心内容。Vuex 的思想是 当我们在页面上点击一个按钮,它会处发(dispatch)一个action, action 随后会执行(commit)一个mutation, mutation 立即会改变state,state改变以后我们的页面会从state获取数据从而改变页面。Store对象包含了我们谈到的所有内容:action, state, mutation,所以是核心了。

示例

使用vue-cli webpack-simple

  1. vue init webpack-simple learn:其中learn为项目名称。
  2. cd learn && cnpm install && npm run dev
  3. cnpm install vuex –save: 安装vuex 附: 在webstorm中新建项目:
  1. 在src下新建display.vue和increment.vue: display.vue:
<template>
    <div>
        <h3>Count is 0</h3>
    </div>
</template>

<script>

</script>

<style scoped>
    h3 {
        font-size: 30px;
    }
</style>

increment.vue:

<template>
    <div>
        <button>+1</button>
        <button>-1</button>
    </div>
</template>

<script>

</script>

<style scoped>
    button  {
        width: 100px;
        height: 100px;
        font-size: 30px;
    }
</style>
  1. 改造App.vue
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <display></display>
    <increment></increment>
  </div>
</template>

<script>
import display from "./display.vue";
import increment from "./increment.vue";

export default {
  data () {
    return {

    }
  },
  components: {
    display,
    increment
  }
}
</script>

<style>
  #app {
    text-align: center;
  }
</style>

运行起来结果如下图:

state

When we say state we are talking about a group of dozens of variables to store data。

state就是页面上用到的各种变量。在写vue页面中,我们经常用{{mes}}取值, mes就是vuex的一个状态。这个状态变化时,页面就会重新渲染。

  1. 用vuex进行状态管理,在src目录下新建store.js文件。我们要在display.vue上添加一个变量count,并对这个变量进行管理。所以在创建store对象的时候,给构造函数传入count参数,且初始化为0。
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count:0
    }
})

export default store
  1. 在App.vue中使用store对象,vue 提供了注入机制,就是把我们的store 对象注入到根实例中。vue的根实例就是new Vue构造函数,然后在所有的子组件中,this.$store来指向store对象。在store.js中我们export store把store暴露出去了,new Vue() 在main.js中,所以直接在main.js中引入store并注入即可。main.js:
import Vue from 'vue'
import App from './App.vue'

import store from "./store.js"  // 引入store 对象

new Vue({
  el: '#app',
  store,  // 注入到根实例中
  render: h => h(App)
})
  1. 改造display.vue,display.vue为App.vue的子组件,所以用this.$store来指向store对象。这样就可以把store.js中的count与display.vue的count绑定了。display.vue:
<template>
    <div>
        <h3>Count is {{count}}</h3>
    </div>
</template>

<script>
    export default {
        computed: {
            count () {
                return this.$store.state.count
            }
        }
    }
</script>
  1. 通过computed属性可以获取到状态值,但是组件中每一个属性(如:count)都是函数,如果有10个,那么就要写10个函数,且重复写10遍return this.$store.state,不是很方便。vue 提供了 mapState 函数,它把state 直接映射到我们的组件中。当然使用mapState 之前要先引入它。它两种用法,或接受一个对象,或接受一个数组。还是在display.vue 组件下。
<template>
    <div>
        <h3>Count is {{count}}</h3>
    </div>
</template>

<script>
    import {mapState} from "vuex";  // 引入mapState

    export default {
      // 下面这两种写法都可以
        computed: mapState({
            count: state => state.count   // 组件内的每一个属性函数都会获得一个默认参数state, 然后通过state 直接获取它的属性更简洁
            count: 'count'         // 'count' 直接映射到state 对象中的count, 它相当于 this.$store.state.count,
        })
    }
</script>

上面的方式还可以使用更简单的数组方式代替。

<script>
    import {mapState} from "vuex";

    export default {
        computed: mapState([   // 数组
            "count"
        ])
    }
</script>

如果我们组件内部也有computed 属性怎么办?它又不属于mapState 中。那就用到了对象分割,把mapState函数生成的对象再分割成一个个的,就像最开始的时候,我们一个一个罗列计算属性,有10个属性,我们就写10个函数。

es6中的... 就是分割用的,但是只能分割数组。在ECMAScript stage-3 阶段它可以分割对象,所以这时还要用到babel-stage-3; npm install babel-preset-stage-3 --save-dev, 安装完全后,一定不要忘记在babelrc 就是babel 的配置文件中,写入stage-3,

否则一直报错。在页面中添加个p标签,显示我们组件的计算属性。

es6下需要在babel.rc文件中配置:

{
  "presets": [
    ["env", {
      "es2015": { "modules": false }
    }],
    "stage-3"   // 一定不要忘记
  ]
}

display.vue:

<template>
    <div>
        <h3>Count is {{count}}</h3>
        <p>组件自己的内部计算属性 {{ localComputed }}</p>
    </div>
</template>

<script>
    import {mapState} from "vuex";
    export default {
        computed: {
            localComputed () {
                return this.count + 10;
            },
            ...mapState({
                count: "count"
            })
        } 
    }
</script>

这时的效果是修改store.js中state.count的值,在display.vue中会重新渲染。将state.count改成20看下效果:

mutation

The only way to actually change state in a Vuex store is by committing a mutation

在vue 中,只有mutation 才能改变state. mutation 类似事件,每一个mutation都有一个类型和一个处理函数,因为只有mutation才能改变state, 所以处理函数自动会获得一个默认参数 state。所谓的类型其实就是名字,action去commit一个mutation, 它要指定去commit哪个mutation, 所以mutation至少需要一个名字。commit mutation 之后,要做什么事情,那就需要给它指定一个处理函数, 类型(名字) + 处理函数就构成了mutation。现在store.js添加mutation:

const store = new Vuex.Store({
    state: {
        count:0
    },
    mutations: {
        // 加1
        INCREMENT(state) {
            state.count++;
        },
        // 减1
        DECREMENT(state) {
            state.count--
        }
    }
})

注意: Vue 建议我们mutation 类型用大写常量表示。

action

需要用action去commit mutations,所以在store.js中添加action定义:

const store = new Vuex.Store({
    state: {
        count:0
    },
    mutations: {
        // 加1
        INCREMENT(state) {
            state.count++;
        },
        // 减1
        DECREMENT(state) {
            state.count--
        }
    },
    actions: {
        increment(context) {
            context.commit("INCREMENT");
        },
        decrement(context) {
            context.commit("DECREMENT");
        }
    }
})

action 和mutions 的定义方法是类似的,我们要dispatch 一个action, 所以actions 肯定有一个名字,dispatch action 之后它要做事情,就是commit mutation, 所以还要给它指定一个函数。因为要commit mutation ,所以 函数也会自动获得一个默认参数context, 它是一个store 实例,通过它可以获取到store 实例的属性和方法。如 context.state 就会获取到 state 属性,context.commit 就会执行commit命令。

actions 还可以简写一下, 因为函数的参数是一个对象,函数中用的是对象中一个方法,我们可以通过对象的解构赋值直接获取到该方法:

actions: {
        increment({commit}){
            commit("INCREMENT")
        },
        decrement({commit}){
            commit("DECREMENT")
        }
    }

dispatch action

什么时候dispatch action 呢?只有当我们点击按钮的时候. 给按钮添加click 事件,在click 事件处理函数的中dispatch action.

打开increment.vue 组件,给两个按钮添加click 事件。

<template>
    <div>
        <button @click="increment">+1</button>
        <button @click="decrement">-1</button>
    </div>
</template>

<script>
    export default {
        methods: {
            increment(){
                this.$store.dispatch("increment");
            },
            decrement() {
                this.$store.dispatch("decrement")
            }
        }
    }
</script>

其实像上面dispatch action 比较麻烦,如果有10 个按钮,我们要写10 个函数,且存在大量的重复,并且我们的事件处理函数名字和action的名字是一样的。vue 提供了mapAction 函数,它和mapState 是一样的,把我们的 action 直接映射到store 里面的action中。像这种组件中的事件处理函数名字和action的名字是相同的,直接把 事件处理函数名字放到一个数组中。组件中的methods 修改如下:

<script>
    import {mapActions} from "vuex";
    export default {
        methods: {
            ...mapActions(["increment", "decrement"])
        }
    }
</script>

如果事件处理函数名字和action的名字不同,给mapActions 提供一个对象,对象的属性是事件处理函数名字, 属性值是 对应的dispatch 的action 的名字。

我们把 +1 按钮的事件处理函数变改为 add,代码如下:

<template>
    <div>
        <button @click="add">+1</button>    <!-- 事件处理函数变为add -->
        <button @click="decrement">-1</button>
    </div>
</template>

<script>
    import {mapActions} from "vuex";
    export default {
        methods: {
            ...mapActions(["decrement"]),

       // mapActions 对应做出改变
            ...mapActions({
                add: "increment"
            })
        }
    }

这时候我们单击按钮,就可以看到count 发生变化。

通过vuex 传递参数

很多时候,组件和组件之间还要传递参数,这些都要经过vuex。在increment 组件内部增加一个输入框和一个按钮,点击按钮的时候,count 增加输入框内的值。

<template>
    <div>
        <div>
            <button @click="increment">+1</button>
            <button @click="decrement">-1</button>
        </div>
    // 增加内容
        <div>
            <input type="text" v-model="incrementValue">
            <button @click="incrementWithValue">increment</button>
        </div>
    </div>
</template>

在组件内部dispatch action 的时候,它可以自定义参数,只要参数在它dispatch 的action名称 后面,依次列出来就可以了。在这里,我们点击按钮的时候,触发一个incrementWithValue action, 并且带一个参数,就可以这样写 this.$store.dispatch(“incrementWithValue”, value), 整个increment.vue 组件如下:

<template>
    <div>
        <div>
            <button @click="increment">+1</button>
            <button @click="decrement">-1</button>
        </div>
        <div>
            <input type="text" v-model="incrementValue">
            <button @click="incrementWithValue">increment</button>
        </div>
    </div>
</template>

<script>
    import {mapActions} from "vuex";
    export default {
        data() {
            return {
                incrementValue: 0
            }
        },
        methods: {
            ...mapActions(["increment","decrement"]),
            incrementWithValue() {
                this.$store.dispatch("incrementWithValue", this.incrementValue)
            }
        }
    }
</script>

同样,actions 和mutations 中的处理函数也是一样,它除了可以得到默认参数外,还可以接受自定义的参数,我们自定义的参数,依次在默认参数后面列出来就可以了。在store.js中分加增加incrementWithValue action和 INCREMENTWITHVALUE mutation:

const store = new Vuex.Store({
    state: {
        count:0
    },
    mutations: {
        // 加1
        INCREMENT(state) {
            state.count++;
        },
        // 减1
        DECREMENT(state) {
            state.count--
        },
        INCREMENT_WITH_VALUE(state, value){
            state.count +=value;
        }
    },
    actions: {
        increment({commit}){
            commit("INCREMENT")
        },
        decrement({commit}){
            commit("DECREMENT")
        },
        incrementWithValue({commit}, value){
            commit("INCREMENT_WITH_VALUE",  parseInt(value))
        }
    }
})

错误处理: 当我们给vuex 传参的时候,我们要检测参数的正确性,如果有错误需要进行处理

action 中如果是同步操作,就用try..catch 语句,组件中使用try…catch, 捕获action中抛出的错误。Increment.vue 组件中,incrementWithValue() 方法中修改如下:

methods: {
            ...mapActions(["increment","decrement"]),
            incrementWithValue() {
                 try {
                     this.$store.dispatch("incrementWithValue", this.incrementValue)
                 }catch(error) {
                     alert(error)
                 }
            }
        }

同时store.js 中的action 也进行如下修改:

incrementWithValue({commit}, value){
    let intValue = parseInt(value);
    if(isNaN(intValue)){
        throw "Not an Interger"
    }else {
        commit("INCREMENT_WITH_VALUE",  intValue)
    }
}

如果action 中进行的是异步操作,那就需要在回调函数中进行错误处理。

incrementWithValue({commit}, value){
      let intValue = parseInt(value)
            setTimeout(function() {

                if(isNaN(intValue)) {
                    alert("Not an Interger")
                }else {    
                    commit("INCREMENT_WITH_VALUE", intValue)
                }
            }, 2000)
        }

异步操作给出用户提示信息: 首先,在我们的increment.vue中添加一个提示信息,简单给一个div 进行提示。用户提示信息的显示和隐藏,又涉及到一个状态,我们设为waiting, 需要在state 中进行添加。默认为false, 同时我们组件需要从state 中获取到初始状态。increment.vue 组件修改如下:

<template>
    <div>
        <div>
            <button @click="increment">+1</button>
            <button @click="decrement">-1</button>
        </div>
        <div>
            <input type="text" v-model="incrementValue">
            <button @click="incrementWithValue">increment</button>
        </div>
        <!-- 展示信息 -->
        <div v-if ="show">
            waiting
        </div>
    </div>
</template>

<script>
    import {mapActions} from "vuex";
    export default {
        data() {
            return {
                incrementValue: 0
            }
        },
    // computed 从state 中获取初始状态
        computed: {
            show: function() {
                return this.$store.state.waiting;
            }
        },
        methods: {
            ...mapActions(["increment","decrement"]),
            incrementWithValue() {
                this.$store.dispatch("incrementWithValue", this.incrementValue)
            }
        }
    }
</script>

mutation 去操作状态,所以增加两个muatation, 用于显示和隐藏waiting. action 中去触发这两个mutation. 整个state 如下:

const store = new Vuex.Store({
    state: {
        count:0,
        // 新增waiting  状态
        waiting: false
    },
    mutations: {
        // 加1
        INCREMENT(state) {
            state.count++;
        },
        // 减1
        DECREMENT(state) {
            state.count--
        },
        INCREMENT_WITH_VALUE(state, value){
            state.count +=value;
        },
        // 显示和隐藏waiting
        SHOW_WAITING_MESSAGE(state){
            state.waiting = true;
        },
        HIDE_WAITING_MESSAGE(state){
            state.waiting = false;
        }
    },
    actions: {
        increment({commit}){
            commit("INCREMENT")
        },
        decrement({commit}){
            commit("DECREMENT")
        },
        incrementWithValue({commit}, value){
            commit("SHOW_WAITING_MESSAGE");
            let intValue = parseInt(value)
            setTimeout(function() {
                if(isNaN(intValue)) {
                    alert("Not an Interger")
                }else {    
                    commit("HIDE_WAITING_MESSAGE");
                    commit("INCREMENT_WITH_VALUE", intValue)
                }
            }, 2000)
        }
    }
})

注意: mutation是同步的,只要comit muation, 它就会立即改变state , 这有利于我们追踪 状态的改变。如果 mution 之后,五分钟才改变state, 那就真不知道state 到底是哪个state了。action 是异步的,还有的错误处理也都在这里操作。

结果: 可以看到将input中的值改成5时,点increment按钮时,上面的display.vue中的值会按input中的步长进行变化。

getters

的组件中获取到 store 中的state, 需要对进行加工才能使用,computed 属性中就需要写操作函数,如果有多个组件中都需要进行这个操作,那么在各个组件中都写相同的函数,那就非常麻烦,这时可以把这个相同的操作写到store 中的getters, 每个组件只要引用getter 就可以了,非常方便。Getter 就是把组件中共有的对state 的操作进行了提取,它就相当于 对state 的computed. 所以它会获得state 作为第一个参数。

假设我们在点击increment按钮的时候,再增加一个额外的数,那么就需要在display.vue 中显示我们多增加了哪个数,同时在increment.vue 中要获得这个数进行相加。state 状态中增加 anotherIncrement: 5.要获取到state 中的这个值, 在每个组件中都要写computed: this.$store.state. countAnother, 这时就可以用getters, 然后在每个组件中computed 中使用getter.

display.vue 修改如下:

<template>
    <div>
        <h3>Count is {{count}}</h3>
        <h3>AnoterIncrement is {{countAnother}}</h3>
    </div>
</template>

<script>
    import {mapState} from "vuex";
    export default {
        computed: {
            ...mapState(["count"]),
            countAnother: function () {  // 获取state
                return this.$store.getters.countAnother;
            }
        }    
    }
</script>

increment.vue 修改如下,这里dispatch中,只能接受一个参数,如果我们要传多个参数的话,需要把所有的参数组装成对象。

<script>
    import {mapActions} from "vuex";
    export default {
        data() {
            return {
                incrementValue: 0
            }
        },
        computed: {
            show: function() {
                return this.$store.state.waiting;
            },
            countAnother: function () { // 获取getters
                return this.$store.getters.countAnother;
            }
        },
        methods: {
            ...mapActions(["increment","decrement"]),
            incrementWithValue() {
          // dispatch 只能接受一个参数,需要传对象参数
                this.$store.dispatch("incrementWithValue", { value:this.incrementValue, anotherValue: this.countAnother})
            }
        }
    }
</script>

store.js 修改如下:

const store = new Vuex.Store({
    state: {
        count:0,
        // 新增waiting  状态
        waiting: false,
        // 额外需要增加的数字
        anotherIncrement: 5
    },
    mutations: {
        // 加1
        INCREMENT(state) {
            state.count++;
        },
        // 减1
        DECREMENT(state) {
            state.count--
        },
        INCREMENT_WITH_VALUE(state, value){
            state.count = state.count + value.intValue + value.anotherValue;
        },
        // 显示和隐藏waiting
        SHOW_WAITING_MESSAGE(state){
            state.waiting = true;
        },
        HIDE_WAITING_MESSAGE(state){
            state.waiting = false;
        }
    },
    actions: {
        increment({commit}){
            commit("INCREMENT")
        },
        decrement({commit}){
            commit("DECREMENT")
        },
        incrementWithValue({commit}, value){
            commit("SHOW_WAITING_MESSAGE");
            let intValue = parseInt(value.value)
            let anotherValue = value.anotherValue
            setTimeout(function() {

                if(isNaN(intValue)) {
                    alert("Not an Interger")
                }else {    
                    commit("HIDE_WAITING_MESSAGE");
                    commit("INCREMENT_WITH_VALUE", {intValue, anotherValue})
                }
            }, 2000)
        }
    },
    getters: {  // getters
        countAnother: function (state) {
            return state.anotherIncrement
        }
    }
})

vuex 也提供了mapGetters 方法,和其的mapState,mapActions 是一样的,如果组件中使用的getters 和store 里面的getters 相同,那就用 数组形式,如果不相同,那就要用对象形式。

increment.vue 修改一下:

computed: {
            show: function() {
                return this.$store.state.waiting;
            },
           ...mapGetters{["countAnother"]} 
}

到这里,vuex 中的 state, action,mutation, getter 等重要概念就介绍完了,但是,我们把所有的getters, actions, mutations 都写到的store 中,如果有很多的话,代码可读性太差,所以就需要 action 创建一个actions.js 文件,mutations 创建一个mutation.js文件,getters 也创建一个getters.js文件,state 作为主要的入口文件命名为index.js,把这四个js文件放到store 文件夹中。

state所在的文件命名为index.js 还和 nodejs 加载模块有关。如果不命名为index.js , 那假设命名为store.js.

在store.js, 我们暴露出通过 new Vuex.Store 构造函数生成的store 对象(export default new Vuex.Store({...})), 这个store 对象需要在 main.js 中引入,然后注入到vue 根实例中。所以在 main.js 中需要写入 import store from './store/store.js', 后面的路径就比较长了。如果我们命名为 index.js, 我们可以直接写 import store from './store', 后面的路径直接到文件夹名就可以了,index.js 可以省略。node 在加载文件夹模块的时候,有如下规定:

var mode = require(“./moduleDir”);

如果moduleDir 是一个文件夹名,Node 就会在指定的文件夹下查找模块。Node 会假定该文件夹是一个包,并试验查找包定义。包定义在名为 package.json 文件中。如果文件夹中没有package.json, 那么就会查找index.js文件,相当于加载 var mode = require(“./moduleDir/index.js”). 如果有package.json 文件,就会查找文件中的 main 属性,如下package.json文件, 相当于加载 var mode = require(“./moduleDir/lib/mymodldule.js”):

{ “name”: “myModule”,  “main” : “./lib/myModule.js”}

在src 目录下,新建store.js 文件夹,里面新建getters.js, actions.js, mutations.js, index.js 文件。

getters.js 文件如下:

export default {
    countAnother: function (state) {
        return state.anotherIncrement
    }
}

actions.js 文件如下:

export default {
    increment({commit}){
        commit("INCREMENT")
    },
    decrement({commit}){
        commit("DECREMENT")
    },
    incrementWithValue({commit}, value){
        commit("SHOW_WAITING_MESSAGE");
        let intValue = parseInt(value.value)
        let anotherValue = value.anotherValue
        setTimeout(function() {

            if(isNaN(intValue)) {
                alert("Not an Interger")
            }else {    
                commit("HIDE_WAITING_MESSAGE");
                commit("INCREMENT_WITH_VALUE", {intValue, anotherValue})
            }
        }, 2000)
    }
}

muations 文件如下:

export default {
    // 加1
    INCREMENT(state) {
        state.count++;
    },
    // 减1
    DECREMENT(state) {
        state.count--
    },
    INCREMENT_WITH_VALUE(state, value){
        state.count = state.count + value.intValue + value.anotherValue;
    },
    // 显示和隐藏waiting
    SHOW_WAITING_MESSAGE(state){
        state.waiting = true;
    },
    HIDE_WAITING_MESSAGE(state){
        state.waiting = false;
    }
}

index.js 文件如下:

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

// 引入actions, mutations, getters
import actions from "./actions.js"
import mutations from "./mutations.js"
import getters from "./getters.js"


const state = {
    count:0,
    // 新增waiting  状态
    waiting: false,
    // 额外需要增加的数字
    anotherIncrement: 5
}

export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters
})

modules

vue 使用的是单一状态树对整个应用的状态进行管理,也就是说,应用中的所有状态都放到store中,如果是一个大型应用,状态非常多, store 就会非常庞大,不太好管理。这时vuex 提供了另外一种方式,可以把整个store 分成几个大的模块,如登录模块,用户模块等,每一个模块都有自己的state, mutation, actions ,getters , 它就相当于是一个小的store,然后我们的根store(通过new Vuex.Store 生成的store) 通过它的modules属性引入这些模块,从而我们的组件就可以使用这些modules 中状态(state).

  1. 新建一个项目体验一下,通过vue –cli新建一个项目vuemodule, 不要忘记安装vuex.
  2. 在src 目录下新一个login文件夹,在里面新建index.js 用于存放login 模块的状态。为了简单起见,我把模块下的state, actions,mutations, getters 全放在index.js文件中。先简单给它增加一个状态,userName: “sam”:
const state = {
    useName: "sam"
};
const mutations = {

};
const actions = {

};
const getters = {

};

// 不要忘记把state, mutations等暴露出去。
export default {
    state,
    mutations,
    actions,
    getters
}
  1. 在src 目录下,再新建一个 store.js 这是根store, 它通过modules 属性引入 login模块。
import Vue from "vue";
import Vuex from "Vuex";

Vue.use(Vuex);

// 引入login 模块
import login from "./login"

export default new Vuex.Store({
    // 通过modules属性引入login 模块。
    modules: {
        login: login
    }
})
  1. 在main.js中引入store, 并注入到vue 根实例中。
import Vue from 'vue'
import App from './App.vue'

// 引入store
import store from "./store"

new Vue({
  el: '#app',
  store,  // 注入到根实例中。
  render: h => h(App)
})
  1. 在 app.vue 中通过computed属性获取到login下的state. 这里要注意,在没有modules 的情况下,组件中通过this.$store.state.属性名 可以获取到,但是有modules 之后,state 被限制到login 的命名空间(模块)下, 所以属性名前面必须加模块名(命名空间),组件中通过 this.$store.state.模块名.属性名,在这里是 this.$store.state.login.userName
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <h1>{{useName}}</h1>
  </div>
</template>

<script>
export default {
  // computed属性,从store 中获取状态state,不要忘记login命名空间。
  computed: {
    useName: function() {
      return this.$store.state.login.useName
    }
  }
}
</script>

组件中成功获取到状态。项目目录和展示如下:

  1. 通过actions, mutations 改变名字, 这就涉及到dispatch action, commit mutations, mutations 改变state. 先在login 文件夹 index.js中添加changeName action 和 CHANGE_NAME mutations.
const mutations = {
    CHANGE_NAME (state, anotherName) {
        state.useName = anotherName;
    }
};

const actions = {
    changeName ({commit},anotherName) {
        commit("CHANGE_NAME", anotherName)
    }
};

在app.vue 中添加一个按钮:change to json, 点击时,dispatch 一个 action. 那在组件中怎么dispatch actions 呢?

在模块中,state 是被限制到模块的命名空间下,需要命名空间才能访问。但actions 和mutations, 其实还有 getters 却没有被限制,在默认情况下,它们是注册到全局命名空间下的,所谓的注册到全局命名空间下,其实就是我们访问它们的方式和原来没有module 的时候是一样的。比如没有module 的时候,this.$store.dispatch(“actions”), 现在有了modules, actions 也写在了module 下面(changeName 写到了login目录下的index.js中),我们仍然可以这么写,this.$store.dispatch(“changeName”), 组件中的getters, 也是通过 this.$store.getters.module中getters 来获取。

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <h1>{{useName}}</h1>
    <!-- 添加按钮 -->
    <div>
      <button @click="changeName"> change to json</button>
    </div>
  </div>
</template>

<script>
export default {
  // computed属性,从store 中获取状态state,不要忘记login命名空间。
  computed: {
    useName: function() {
      return this.$store.state.login.useName
    }
  },
  methods: {

  // 和没有modules的时候一样,同样的方式dispatch action
    changeName() {
      this.$store.dispatch("changeName", "Jason")
    }
  }
}
  1. 局部参数 虽然dispatch action和 commit mutations 可以全局使用,但是写在module 中的actions, mutations 和getters, 它们获得的默认参数却不是全局的,都是局部的,被限定在它们所在的模块中的。比如mutations和getters 会获得state 作为第一个默认参数,这个state参数,就是限定在mutations 和getters 所在模块的state对象,login 文件夹下的mutations 和getters 只会获取到当前index.js 中的 state 作为参数 。actions 会获得一个context 对象作为参数,这个context 对象就是当前module 的实例,module 相当于一个小store.

那么怎样才能获取到根store 中的state 和 getters 呢?Vuex 提供了 rootState, rootGetters 作为module 中 getters 中默认参数, actions中context 对象,也会多了两个属性,context.getters, context. rootState, 这些全局的默认参数,都排在局部参数的后面。

我们在store.js中添加 state, getters:

export default new Vuex.Store({
    // 通过modules属性引入login 模块。
    modules: {
        login: login
    },

    // 新增state, getters
    state: {
        job: "web"
    },
    getters: {
        jobTitle (state){
            return state.job + "developer"
        }
    }
})

login 目录下的 index.js

const actions = {
    // actions 中的context参数对象多了 rootState 参数
    changeName ({commit, rootState},anotherName) {
        if(rootState.job =="web") {
            commit("CHANGE_NAME", anotherName)
        }
    }
};

const getters = {
    // getters 获取到 rootState, rootGetters 作为参数。
    // rootState和 rootGetter参数顺序不要写反,一定是state在前,getter在后面,这是vuex的默认参数传递顺序, 可以打印出来看一下。
    localJobTitle (state,getters,rootState,rootGetters) {  
        console.log(rootState);
        console.log(rootGetters);
        return rootGetters.jobTitle + " aka " + rootState.job
    }
};

app.vue 增加h2 展示 loacaJobTitle, 这个同时证明了getters 也是被注册到全局中的。

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <h1>{{useName}}</h1>

    <!-- 增加h2 展示 localJobTitle -->
    <h2>{{localJobTitle}}</h2>
    <!-- 添加按钮 -->
    <div>
      <button @click="changeName"> change to json</button>
    </div>
  </div>
</template>

<script>
import {mapActions, mapState,mapGetters} from "vuex";
export default {
  // computed属性,从store 中获取状态state,不要忘记login命名空间。
  computed: {
    ...mapState({
      useName: state => state.login.useName
    }),

    // mapGeter 直接获得全局注册的getters
    ...mapGetters(["localJobTitle"])
  },
  methods: {
    changeName() {
      this.$store.dispatch("changeName", "Jason")
    }
  }
}
</script>

结果:

  1. 其实actions, mutations, getters, 也可以限定在当前模块的命名空间中。只要给我们的模块加一个namespaced 属性:
const state = {
    useName: "sam"
};
const mutations = {
    CHANGE_NAME (state, anotherName) {
        state.useName = anotherName;
    }
};
const actions = {
    changeName ({commit, rootState},anotherName) {
        if(rootState.job =="web") {
            commit("CHANGE_NAME", anotherName)
        }
    },
    alertName({state}) {
        alert(state.useName)
    }
};
const getters = {
    localJobTitle (state,getters,rootState,rootGetters) {  
        return rootGetters.jobTitle + " aka " + rootState.job
    }
};
// namespaced 属性,限定命名空间
export default {
    namespaced:true,
    state,
    mutations,
    actions,
    getters
}

当所有的actions, mutations, getters 都被限定到模块的命名空间下,我们dispatch actions, commit mutations 都需要用到命名空间。如 dispacth("changeName"), 就要变成 dispatch("login/changeName"); getters.localJobTitle 就要变成 getters["login/localJobTitle"] app.vue 如下:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <h1 @click ="alertName">{{useName}}</h1>

    <!-- 增加h2 展示 localJobTitle -->
    <h2>{{localJobTitle}}</h2>
    <!-- 添加按钮 -->
    <div>
      <button @click="changeName"> change to json</button>
    </div>
  </div>
</template>

<script>
import {mapActions, mapState,mapGetters} from "vuex";
export default {
  // computed属性,从store 中获取状态state,不要忘记login命名空间。
  computed: {
    ...mapState("login",{
      useName: state => state.useName
    }),

     localJobTitle() {
       return this.$store.getters["login/localJobTitle"]
     }
  },
  methods: {
    changeName() {
      this.$store.dispatch("login/changeName", "Jason")
    },
    alertName() {
      this.$store.dispatch("login/alertName")
    }
  }
}
</script>

有了命名空间之后,mapState, mapGetters, mapActions 函数也都有了一个参数,用于限定命名空间,每二个参数对象或数组中的属性,都映射到了当前命名空间中。

<script>
import {mapActions, mapState,mapGetters} from "vuex";
export default {
  computed: {
    // 对象中的state 和数组中的localJobTitle 都是和login中的参数一一对应。
    ...mapState("login",{
      useName: state => state.useName
    }),
    ...mapGetters("login", ["localJobTitle"])
  },
  methods: {
    changeName() {
      this.$store.dispatch("login/changeName", "Jason")
    },
    ...mapActions('login', ['alertName'])
  }
}
</script>

参考

  1. https://www.cnblogs.com/SamWeb/p/6527240.html
  2. https://www.cnblogs.com/SamWeb/p/6543931.html
  3. https://www.cnblogs.com/SamWeb/p/6560971.html
  4. https://www.cnblogs.com/SamWeb/p/6590508.html

原文发布于微信公众号 - 开发架构二三事(gh_d6f166e26398)

原文发表时间:2019-07-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券