vue状态管理、Vuex使用详解

1.NPM安装

    npm install vuex --save

2.配置vuex

Vuex 实例对象属性 主要有下面5个核心属性

    state :全局访问的state对象,存放要设置的初始状态名及值(必须要有)

    mutations :里面可以存放改变 state 的初始值的方法 ( 同步操作--必须要有 )

    getters :实时监听state值的变化可对状态进行处理,返回一个新的状态,相当于store的计算属性(不是必须的)

    actions :里面可以存放用来异步触发 mutations 里面的方法的方法 ( 异步操作--不是必须的 )

    modules :存放模块化的数据(不是必须的)

全局配置Vuex:

    在 src 目录下创建 store 文件夹,并在里面创建一个index.js文件,然后index.js中配置如下:

第一步:引入Vue、和Vuex(固定写法)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex); 

第二步:声明Vuex 的五个属性,其中state,mutations 是一定要定义的,其他的三个属性对象根据实际需要。
const state = {  // 初始化状态值--一定要有该属性对象         
    ...
}
const mutations = {  // 自定义改变state初始值的方法--一定要有该属性对象
    ...
}
const getters = {  // 状态计算属性--该属性对象不是必须的            
    ...
}
const actions = { // 异步操作状态--该属性对象不是必须的
    ...
}
const modules = {  // 状态模块--该属性对象不是必须的
    ...
}

第三步:创建一个 store 实例,将声明的五个变量赋值赋值给 store 实例,如下:
const store = new Vuex.Store({
   state,
   mutations,
    //下面三个非必须
   getters,
   actions,
   modules
})

第四步:导出 store 实例,供外部访问
export default store

在项目的main.js中将Vuex注册到全局实例中

...
import store from './store'
...

new Vue({
  el: '#app',
  router,
  store,         //注入,组件中可以使用 this.$store 获取
  components: { App },
})

上面只是讲了Vuex的全局配置,给我们打好了骨架,那么接下来认识一下,Vuex中五个核心属性的具体用法以及什么时候会用到 非必须的三个属性对象  getters,actions,modules

3.vuex使用

  • state属性

       在配置文件store/index.js中,比如初始化设置两个状态 StudNumStudScore

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

const state = {      
    StudNum:3, // 初始化一个状态,存放学生人数
    StudScore:[ // 初始化一个状态,存放学生的分数信息
        {name:'小敏',score:80},
        {name:'小花',score:90},
        {name:'小红',score:98}
    ]
}

const store = new Vuex.Store({
   state
})
export default store

在组件中获取两个状态的值:this.$store.state.xxx

<template>
<div>
    <h4>直接使用状态值</h4>
    <p>学生人数:{{$store.state.StudNum}}</p>
    <p v-for="(item,index) in $store.state.StudScore" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
<!------------------------->
    <h4>通过计算属性获取</h4>
    <p>学生人数:{{StudNum}}</p>
    <p v-for="(item,index) in StudScore" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
</div>
</template>
<script>
export default{
    computed:{ // 计算属性
        StudNum(){
             return this.$store.state.StudNum
        },
        StudScore(){
             return this.$store.state.StudScore
        },
    }
}
</script>

页面效果如下:

state使用
state使用

注:两种方式都可以获取状态值。效果是一样的:直接使用状态,或者在vue计算属性中使用。  接下来的获取状态值,都是用vue计算属性,方便后面讲解辅助函数

mapState 辅助函数

        当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键。上面计算属性获取状态值用辅助函数可写法如下:

<template>
<div>
    <h4>通过计算属性获取</h4>
    <p>学生人数:{{StudNum}}</p>
    <p v-for="(item,index) in StudScore" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
</div>
</template>
<script>
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
    data(){
        return{
              localCount:12
        }
    },
    computed: mapState({
        StudNum: state => state.StudNum,  // 箭头函数可使代码更简练
        StudScore: 'StudScore',           // 可也传字符串参数 'StudScore' 等同于 `state =>state.StudScore
        nweNum(state) {  // 为了能够使用 `this` 获取局部状态,必须使用常规函数纠正this指向
            return state.count + this.localCount
        }
    })
}
</script>

         当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组,上例中的计算属性StudNumStudScore跟state中的子节点状态名相同,因此可简写成如下写法:

computed: mapState([
  'StudNum',   // 映射 this.StudNum为 this.$store.state.StudNum
  'StudScore'  // 映射 this.StudScore为 this.$store.state.StudScore
])

对象展开运算符

   mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符(现处于 ECMAScript 提案 stage-4 阶段),我们可以极大地简化写法:后面讲解案例用到辅助函数时都会用这种方法,也推荐使用这种写法来使用辅助函数。当然使用辅助函数也不是必须的,对于单个组件中用到的状态比较多时,使用辅助函数是个很好的选择,能极大的简化代码。具体写法如下:

computed: {
  localComputed () { // 组件中的其他计算属性
       return 23
  },
  ...mapState([  // 使用对象展开运算符将此对象混入到外部对象中
      'StudNum',   // 映射 this.StudNum为 this.$store.state.StudNum
      'StudScore'  // 映射 this.StudScore为 this.$store.state.StudScore
  ])
}

        vuex中的辅助函数有四种,mapState, mapGetters,mapMutations, mapActions,他们的用法一模一样,掌握了mapState的用法,其他的三个用法也就掌握了。


  • getters属性

    前面说了,getters不是必须的,那么什么时候会用到呢?有时候我们需要从 store 中的 state 中派生出一些状态(对state进行计算过滤等操作),例如上例,我们在getters中从state的子节点StudScore里派生出两个状态:90分以上的学生以及其数量:

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

const state = {      
    StudNum:2, // 初始化一个状态,代表学生人数
    StudScore:[ // 初始化一个状态,存放学生的分数信息
        {name:'小敏',score:80},
        {name:'小花',score:90},
        {name:'小红',score:98}
    ]
}
const getters = {
    // 获取分数为90分以上的学生
    perfect: state => { // 过滤分数,获取90分及以上的学生
        return state.StudScore.filter(Stud => Stud.score>=90)
    },
    // 获取分数为90分以上的学生数量
    perfectNum: (state,getters) => { // getters 也可以接受其他 getters 作为第二个参数
        return getters.perfect.length
    }
};

const store = new Vuex.Store({
   state,
   getters
})
export default store

在组件中获取getters派生的两个状态的值:this.$store.getters.xxx

<template>
<div>
    <h4>state原始状态</h4>
    <p>学生人数:{{$store.state.StudNum}}</p>
    <p v-for="(item,index) in $store.state.StudScore" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
<!------------------------->
    <h4>getters 派生的状态 通过属性访问</h4>
    <p>优秀学生人数:{{$store.getters.perfectNum}}</p>
    <p v-for="(item,index) in $store.getters.perfect" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default{
    computed:{
        ...mapState([  // 使用辅助函数 mapState 
            'StudNum',   // 映射 this.StudNum为 this.$store.state.StudNum
            'StudScore'  // 映射 this.StudScore为 this.$store.state.StudScore
        ])
    }
}
</script>

页面效果如下: 

getters使用 属性访问

 

      从上面例子可以看出,使用getters我们从state子节点StudScore中派生了两个新的状态,这两个派生出的新状态不用在state中进行初始化,这是getters以属性的形式返回的情况,getters也可以返回一个函数,通过函数来访问getters,我们紧接着上面的例子,在getters中再派生出一个状态:checkScore 通过分数找出学生信息,它返回的是一个函数,如下:

...
const getters = {
    ....
    // 用分数查询学生信息
    checkScore:state=>n=>{ // 返回一个方法函数
        return state.StudScore.find(Stud=> Stud.score=== n)
    }
}
...

在组件中通过方法访问getters派生的状态:this.$store.getters.xxx( val ) 

<template>
<div>
    <h4>state原始状态</h4>
    <p>学生人数:{{StudNum}}</p>
    <p v-for="(item,index) in StudScore" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
<!------------------------->
    <h4>getters 派生的状态 通过属性访问</h4>
    <p>优秀学生人数:{{$store.getters.perfectNum}}</p>
    <p v-for="(item,index) in $store.getters.perfect" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
<!------------------------->
    <h4>getters 派生的状态 通过方法访问</h4>
    <p>有没有人得98分</p>
    <p>{{$store.getters.checkScore(98)}}</p>
    </div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default{
    computed:{
        ...mapState([  // 使用辅助函数 mapState 
            'StudNum',   // 映射 this.StudNum为 this.$store.state.StudNum
            'StudScore'  // 映射 this.StudScore为 this.$store.state.StudScore
        ])
    }
}
</script>

页面效果如下:

getters使用  方法访问

 

    注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的,即只要对应的state状态不发生改变,不管执行多少次getters,都会从缓存中获取getters的状态值,不会重新计算,一旦对应的state发生改变,getters就会重新计算,并缓存起来。

               getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。即不管对应的状态有没有发生改变,访问一次getters,就会执行一次getters返回的函数,并且不会被缓存。

    通过上面getters的使用,我们可以看到对状态进行计算操作,我们不一定非使用getters不可,我们也可以在组件中获取状态值,再对得到的值进行过滤计算等操作也是可以的,所以说getters不是必须的。

mapGetters 辅助函数

    mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

import { mapState, mapGetters } from 'vuex'

export default {
  computed: {
        ...mapState([    // 使用辅助函数 mapState 
            'StudNum',   // 映射 this.StudNum为 this.$store.state.StudNum
            'StudScore'  // 映射 this.StudScore为 this.$store.state.StudScore
        ]),
        ...mapGetters([  // 使用辅助函数 mapGetters
            'perfect',   // 映射 this.perfect为 this.$store.getters.perfect
            'perfectNum',// 映射 this.perfectNum为 this.$store.getters.perfectNum
            'checkScore' // 映射 this.checkScore为 this.$store.getters.checkScore()
        ])
  }
}

如果你想将一个 getter 属性另取一个名字,使用对象形式:

computed: {
    ...mapGetters({
        P:'perfect',    // 把 `this.P` 映射为 `this.$store.getters.perfect`
        N:'perfectNum', // 把 `this.N` 映射为 `this.$store.getters.perfectNum`
        S:'checkScore'  // 把 `this.S()` 映射为 `this.$store.getters.checkScore()`
    })
}

  • mutations

     更改 Vuex 的 store 中的状态的唯一方法是提交 mutation ,所以他是必须的。现在来看一下,怎么更改store中的状态,我们再次继续上面的学生分数信息的例子,我们在mutations里面,初始化一个方法:ChangeStudScore ,在此方法里面根据外部传参,我们重新改变state中StudNum STudScore 的值,如下:

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

const state = {      
    StudNum:2, // 初始化一个状态,代表学生人数
    StudScore:[ // 初始化一个状态,存放学生的分数信息
        {name:'小敏',score:80},
        {name:'小花',score:90},
        {name:'小红',score:98}
    ]
}
const getters = {
    // 获取分数为90分以上的学生
    perfect: state => { // 过滤分数,获取90分及以上的学生
        return state.StudScore.filter(Stud => Stud.score>=90)
    },
    // 获取分数为90分以上的学生数量
    perfectNum: (state,getters) => { // getters 也可以接受其他 getters 作为第二个参数
        return getters.perfect.length
    },
    // 用分数查询学生信息
    checkScore:state=>n=>{ // 返回一个方法函数
        return state.StudScore.find(Stud=> Stud.score=== n)
    }
};
const mutations={
    ChangeStudScore(state,obj) {   //自定义改变state初始值的方法,这里面的参数除了state之外还可以再传额外的参数(变量或对象);
        state.StudNum = obj.length; // 更改状态StudNum 的值
        state.StudScore = obj; // 更改状态StudScore 的值
    }
}

const store = new Vuex.Store({
   state,
   getters,
   mutations
})
export default store

在组件中使用mutations中的方法来改变状态的值(顺便把辅助函数一起讲解): this.$store.commit( "xxx" )

<template>
<div>
<button @click="add">点击改变--></button>
    <h4>state原始状态</h4>
    <p>学生人数:{{StudNum}}</p>
    <p v-for="(item,index) in StudScore" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
<!------------------------->
    <h4>getters 派生的状态 通过属性访问</h4>
    <p>优秀学生人数:{{perfectNum}}</p>
    <p v-for="(item,index) in perfect" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
<!------------------------->
    <h4>getters 派生的状态 通过方法访问</h4>
    <p>有没有人得98分</p>
    <p>{{checkScore(98)}}</p>
    </div>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default{
    computed:{
  computed: {
        ...mapState([    // 使用辅助函数 mapState 
            'StudNum',   // 映射 this.StudNum为 this.$store.state.StudNum
            'StudScore'  // 映射 this.StudScore为 this.$store.state.StudScore
        ]),
        ...mapGetters([  // 使用辅助函数 mapGetters
            'perfect',   // 映射 this.perfect为 this.$store.getters.perfect
            'perfectNum',// 映射 this.perfectNum为 this.$store.getters.perfectNum
            'checkScore' // 映射 this.checkScore为 this.$store.getters.checkScore
        ])
  }
    },
    methods: {
        ...mapMutations([// 使用辅助函数 mapMutations
            "ChangeStudScore" // 映射 this.ChangeStudScore(obj)为 this.$store.commit("ChangeStudScore", obj)
        ]),
        add() { // 点击按钮,设置状态的值
            let obj = [
                {name: '张三',score: 93},
                {name: '李四',score: 90},
                {name: '王五',score: 98},
                {name: '赵六',score: 70},
            ]
            //this.$store.commit("ChangeStudScore", obj) // 不使用辅助函数时的写法
            this.ChangeStudScore(obj)// 使用辅助函数时的写法
        }
    },
}
</script>
		

页面展示效果: 

mutations使用

使用常量替代 Mutation 事件类型( 直接复制官网,实际开发中没用过 )

     使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。

Mutations 必须是同步函数:

       一条重要的原则就是要记住 mutations 必须是同步函数.

const mutations= {
  someMutation (state) {
    api.callAsyncMethod(() => {      //当这个 callAsyncMethod 请求函数执行完毕时 让 state.count++
      state.count++
    })
  }
}

       现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

       关于Mutation 必须是同步函数,上面的例子和说明是官方的原话,但是我自己测试时,在Mutation 里面使用异步操作是可以改变state状态值的。如下:

const mutations= {
  someMutation (state) {
    setTimeout(() => {      //模拟异步函数,延迟5s后执行 state.count++
      state.count++
    },5000)
  }
}

      在组件中触发this.$store.commit("someMutation "), 5s后状态更新了,并没有什么问题,所以官网说Mutation 必须是同步函数,暂时无法理解是什么意思。既然官网都说了,异步操作不要在mutations 里面使用,那只好遵循官网的规定,并且关于异步操作,Vuex有专门的方法,使用 actions。

Mutation 需遵守 Vue 的响应规则

      既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性。

  2. 当需要在对象上添加新属性时,你应该

  • actions

Action 类似于 mutation,不同在于:

  1. Action 提交的是 mutation,而不是直接变更状态。
  2. Action 可以包含任意异步操作。

    在上面讲解mutations时,官方说了不要在mutations里面使用异步操作,这里我们来看一下,专门用来处理异步操作的actions方法

我们继续上面学生考分的例子,我们在actions里面初始化一个方法

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

const state = {      
    StudNum:2, // 初始化一个状态,代表学生人数
    StudScore:[ // 初始化一个状态,存放学生的分数信息
        {name:'小敏',score:80},
        {name:'小花',score:90},
        {name:'小红',score:98}
    ]
}
const getters = {
    // 获取分数为90分以上的学生
    perfect: state => { // 过滤分数,获取90分及以上的学生
        return state.StudScore.filter(Stud => Stud.score>=90)
    },
    // 获取分数为90分以上的学生数量
    perfectNum: (state,getters) => { // getters 也可以接受其他 getters 作为第二个参数
        return getters.perfect.length
    },
    // 用分数查询学生信息
    checkScore:state=>n=>{ // 返回一个方法函数
        return state.StudScore.find(Stud=> Stud.score=== n)
    }
}
const mutations={
    ChangeStudScore(state,obj) {   //自定义改变state初始值的方法,这里面的参数除了state之外还可以再传额外的参数(变量或对象);
        state.StudNum = obj.length; // 更改状态StudNum 的值
        state.StudScore = obj; // 更改状态StudScore 的值
    }
}
const actions = {
    AsyncChangeStudScore(context) {
      // 模拟异步请求,5秒后获取导数据,然后触发mutations中的方法ChangeStudScore,并传值
      setTimeout(()=>{
          let obj = {
                {name: '张三',score: 93},
                {name: '李四',score: 90},
                {name: '王五',score: 98},
                {name: '赵六',score: 70},
          }
          context.commit('ChangeStudScore',obj)
      },5000)

    }
}
const store = new Vuex.Store({
   state,
   getters,
   mutations
})
export default store

     在组件中调用actions方法( 辅助函数一起讲解 ):this.$store.dispatch( "xxx", obj ) 

<template>
<div>
<button @click="add">点击改变--></button>
    <h4>state原始状态</h4>
    <p>学生人数:{{StudNum}}</p>
    <p v-for="(item,index) in StudScore" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
<!------------------------->
    <h4>getters 派生的状态 通过属性访问</h4>
    <p>优秀学生人数:{{perfectNum}}</p>
    <p v-for="(item,index) in perfect" :key="index">
        姓名:{{item.name}} | 分数:{{item.score}}
    </p>
<!------------------------->
    <h4>getters 派生的状态 通过方法访问</h4>
    <p>有没有人得98分</p>
    <p>{{checkScore(98)}}</p>
    </div>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default{
    computed:{
  computed: {
        
        ...mapState([    // 使用辅助函数 mapState 
           'StudNum',   // 映射 this.StudNum为 this.$store.state.StudNum
           'StudScore'  // 映射 this.StudScore为 this.$store.state.StudScore
        ]),
        ...mapGetters([  // 使用辅助函数 mapGetters
            'perfect',   // 映射 this.perfect为 this.$store.getters.perfect
            'perfectNum',// 映射 this.perfectNum为 this.$store.getters.perfectNum
            'checkScore' // 映射 this.checkScore为 this.$store.getters.checkScore
        ])
  }
    },
    methods: {
/*   不直接使用mutations改变状态,使用下面的异步请求到的数据来更新状态
        ...mapMutations([ // 使用辅助函数 mapMutations
            "ChangeStudScore" // 映射 this.ChangeStudScore(obj)为 this.$store.commit("ChangeStudScore", obj)
        ]),
        add() { // 点击按钮,设置状态的值
            let obj = [
                {name: '张三',score: 93},
                {name: '李四',score: 90},
                {name: '王五',score: 98},
                {name: '赵六',score: 70},
            ]
            //this.$store.commit("ChangeStudScore", obj) // 不使用辅助函数时的写法
            this.ChangeStudScore(obj)// 使用辅助函数时的写法
        }
*/
        ...mapActions([  // 使用辅助函数 mapMutations
            'AsyncChangeStudScore'
        ]),
        add() { // 点击按钮,设置状态的值
            //this.$store.dispatch("AsyncChangeStudScore") // 不使用辅助函数时的写法
            this.AsyncChangeStudScore(obj)// 使用辅助函数时的写法
        }
    },
}
</script>

    页面中最终展示的效果,跟讲解mutations 时显示的信息是一模一样的,只不过是这里采用了异步获取数据来更新状态,即点击按钮后,先进行异步请求操作,5秒后将异步获取成功的新的数据更新到状态里面,此时视图页面也跟着更新。

当然actions含有其他许多用法,下面来看一下actions的其他介绍(官网复制而来)

       让我们来注册一个简单的 action

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

       Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

分发 Action

       Action 通过 store.dispatch 方法触发:

store.dispatch('increment')

      乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

Actions 支持同样的载荷方式和对象方式进行分发:

// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

来看一个更加实际的购物车示例,涉及到调用异步 API 和分发多重 mutation

actions: {
  checkout ({ commit, state }, products) {
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求,然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)。

在组件中分发 Action

    你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

组合 Action

          Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

现在你可以:

store.dispatch('actionA').then(() => {
  // ...
})

在另外一个 action 中也可以:

actions: {
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

最后,如果我们利用 async / await,我们可以如下组合 action:

// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

  • modules

     由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

       为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

模块的局部状态

对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

命名空间

     默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

const store = new Vuex.Store({
    modules: {
        account: {
            namespaced: true,
            // 模块内容(module assets)
            state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
            getters: {
                isAdmin () { ... } // -> getters['account/isAdmin']
            },
            actions: {
                login () { ... } // -> dispatch('account/login')
            },
            mutations: {
                login () { ... } // -> commit('account/login')
            },
            
            // 嵌套模块
            modules: {
                // 继承父模块的命名空间
                myPage: {
                    state: { ... },
                    getters: {
                        profile () { ... } // -> getters['account/profile']
                    }
                },
                // 进一步嵌套命名空间
                posts: {
                    namespaced: true,
                    state: { ... },
                    getters: {
                        popular () { ... } // -> getters['account/posts/popular']
                    }
                }
            }

        }
    }
})

启用了命名空间的 getter 和 action 会收到局部化的 getterdispatch 和 commit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。

#在带命名空间的模块内访问全局内容(Global Assets)

如果你希望使用全局 state 和 getter,rootState 和 rootGetter 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。

若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。

modules: {
  foo: {
    namespaced: true,
    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

在带命名空间的模块注册全局 action

      若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,
      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}

带命名空间的绑定函数

        当使用 mapStatemapGettersmapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:

computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

        对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

        而且,你可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

给插件开发者的注意事项

        如果你开发的插件(Plugin)提供了模块并允许用户将其添加到 Vuex store,可能需要考虑模块的空间名称问题。对于这种情况,你可以通过插件的参数对象来允许用户指定空间名称:

// 通过插件的参数对象得到空间名称
// 然后返回 Vuex 插件函数
export function createPlugin (options = {}) {
  return function (store) {
    // 把空间名字添加到插件模块的类型(type)中去
    const namespace = options.namespace || ''
    store.dispatch(namespace + 'pluginAction')
  }
}

模块动态注册

        在 store 创建之后,你可以使用 store.registerModule 方法注册模块:

// 注册模块 `myModule`
store.registerModule('myModule', {
  // ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})

之后就可以通过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。

模块动态注册功能使得其他 Vue 插件可以通过在 store 中附加新模块的方式来使用 Vuex 管理状态。例如,vuex-router-sync 插件就是通过动态注册模块将 vue-router 和 vuex 结合在一起,实现应用的路由状态管理。

你也可以使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建 store 时声明的模块)。

保留 state

        在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState 选项将其归档:store.registerModule('a', module, { preserveState: true })

当你设置 preserveState: true 时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。

模块重用

有时我们可能需要创建一个模块的多个实例,例如:

如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。

实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):

const MyReusableModule = {
  state () {
    return {
      foo: 'bar'
    }
  },
  // mutation, action 和 getter 等等...
}

4.配置使用总结

       在 src 目录下创建 store 文件夹,并在里面创建一些文件,结如

src
 ├── ....
 ├── main.js
 └── store
       ├── modules  // 模块化
       │      ├── page1.js 
       │      └── page2.js 
       └── index.js 

a) store/index.js 中的内容: 

      开始配置主文件store / index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex); 
import page1 from './modules/page1' // 将page1.js文件导入
import page2 from './modules/page2' // 将page1.js文件导入
const state = {          
  showFooter: true,
  obj:{ b:2 }   
}
const mutations = {  
  showFooter(state,val) {
    state.showFooter = val;
  },
  obj(state,val){ 
    state.obj = val;
  }
}
const getters = { 
  showFooter(state) {  
    return state.showFooter 
  },
  obj(){                       
    return state.obj 
  }
}
const actions = {
  showFooter(context,val) { 
    setTimeout(()=>{
        context.commit('showFooter',val); 
    },3000)      
  },
  obj(context,val){                 
    context.commit('obj',val)  
  }
}
const modules = {
  page1,
  page2
}
const store = new Vuex.Store({
   state,
   getters,
   mutations,
   actions,
   modules
})
export default store

b).store / page1.js 中的内容:( page1.js使用的是普通语法,数据结构和 page2.js一样)

      模块js中的 5个属性的定义,与index.js 中的一样

export default {
  namespaced:true,       //用于在全局引用此文件里的方法时标识这一个的文件名
  state:{
    arr:["a"],           //初始化一个 arr 数组
  },
  getters:{
    getArr:function(state){              
      return state.arr
    }
  },
  mutations:{
    pushArr:function(state,items){      
      state.arr.push(items)
    }
  },
  actions:{
    pushArrRun:function(context,item){   
      context.commit('pushArr',item)
    }
  }
}

c). store / page2.js 中的内容:( page2.js使用的是ES6语法。page1.js,page2.两个写法一样,看个人喜好,推荐使用ES6语法)

const state={         
  sum:1   //初始化一个 sum
}
const getters = {      
  getSum: () => state.sum
}
const mutations = {    
  add: (state,m) => state.sum += m 
}
const actions = {      
  addTo:(context,m) => context.commit('add',m)
}
export default {
  namespaced: true,    
  state,
  getters,
  mutations,
  actions
}

d). 最后一步   在 main.js 中将 配置好的Vuex 挂载(注入)到vue 实例中

//.......
import store from './store'

new Vue({
  el: '#app',
  router,
  store,         //注入,组件中可以使用 this.$store 获取
  components: { App },
})

到此,Vuex 就配置完成了,简单大致梳理一下配置流程:

    1.声明 state 对象,存放要初始状态的数据对象

    2.声明 getters 对象,存放 派生state中数据的方法(store计算属性)

    3.声明 mutations 对象,存放 设置 state中数据的方法(同步操作)

    4.声明 actions 对象,存放 异步操作 mutations中的方法的方法(异步操作)

    5.声明 modules 对象, 存放 外部模板 文件 (如果没有创建外部 js 文件,次步可去除)

    6.创建 Vuex 实例,注入上面声明的对象,并用export default导出,然后再在main.js中将 Vuex 挂在到Vue实例中 

再简单看一下基本使用的语法:

    1.获取 某个状态的值:  index.js   中的 obj 的值:this.$store.state.obj

                                        index.js   中的 obj 的值:this.$store.getters.obj

                                        page1.js  中的 arr 的值:this.$store.state[ "page1/arr" ]

                                        page1.js  中的 arr 的值:this.$store.getters[ "page1/arr" ]

    2.设置 某个状态的值:  index.js   中的 obj 的值:this.$store.commit( "obj" , val )

                                        index.js 中的 obj 的值:this.$store.dispatch( "obj" , val )

                                        page1.js 中的 arr 的值:this.$store.commit( "page1/arr" , val )

                                        page1.js 中的 arr 的值:this.$store.dispatch( "page1/arr" , val )

1.基本语法使用


<script>
export default {
  data () {
    return { }
  },
  computed:{
    objData(){ // 获取 store/index.js 中的 obj
      return this.$store.state.obj  
或
      return this.$store.getters.obj                  
    },
    arrData(){ // 获取 store/module/page1.js 中的 arr 
      return this.$store.state['page1/getArr']    
或 
      return this.$store.getters['page1/getArr']  
    },
    sumData(){ // 获取 store/module/page2.js 中的 sum
      return this.$store.state['page2/getSum']
或
      return this.$store.getters['page2/getSum']        
    }
  },
  methods:{  
    getDatas(s){ 
      // 获取 store/index.js 中对象 ctions的obj方法改变状态的值
      this.$store.dispatch('obj', s)
    },
    pushArr(s){  
      // 获取 store/module/page1.js 中对象actions的pushArrRun方法改变状态的值
      this.$store.dispatch('page1/pushArrRun', s)    
    },
    add(s){      
      // 获取 store/module/page2.js 中对象actions的addTo 方法改变状态的值
      this.$store.dispatch('page2/addTo', s)         
    },
    push:function(e){
      this.pushArr(e.target.innerHTML)
      this.add(1)
      this.getDatas({
        total: this.sumData,
        list: this.arrData
      }) 
    }
  }
}
</script>

2.辅助函数 mapStatemapGettersmapActions 和 mapMutations

<template>
  <div class="test">
    <button @click="push">拖鞋</button>
    <button @click="push">上衣</button>
    <button @click="push">帽子</button>
    <p>所有物品:{{ arrData }}</p>
    <p>总件数:{{ sumData }}</p>
    <p>总信息:{{ objData }}</p>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex'  // 引入三个方法,个人觉得mapState多余,几乎用不到。
export default {
  data () {
    return {
    }
  },
  computed:{
    ...mapState({                   
      objData:state=>state.obj,        
      arrData:state=>state.page1.arr,  
      sumData:state=>state.page2.sum 
    }),
    ...mapGetters({              
      objData:'obj',                     
      arrData:'page1/getArr',          
      sumData:'page2/getSum'           
    })
  },
  methods:{ 
    ...mapMutations({             
      newObjRun:'obj',                 
      pushArrRun:'page1/pushArrRun',   
      addTo:'page2/addTo'              
    }),
    ...mapActions({             
      newObjRun:'obj',                 
      pushArrRun:'page1/pushArrRun',   
      addTo:'page2/addTo'              
    })
  }
}
</script>

 

传送门:

vue 完整项目配置1(下载/安装)

vue项目配置2-项目目录结构,vue打包白屏,图片加载不出等问题

vue项目配置3-router路由配置,元信息meta的使用-登录拦截验证

vue项目配置5-axios请求 配置

  • 25
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值