Vuex 的详细介绍请查看官方文档 :https://vuex.vuejs.org/zh/ 

前言:写这个教程的原因是自己和 Vuex 打交道的时候踩过很多坑,所以记录一下学习的过程,还有是官方的教程真的是太官方了,对我这种小菜鸟来说不是那么的通俗易懂。

1、Vuex 是什么

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

// 也就是说

* VueX是适用于在Vue项目开发时使用的状态管理工具。

// 什么情况下使用 

试想一下,如果在一个项目开发中频繁的使用组件传参的方式来同步data中的值,一旦项目变得很庞大,管理和维护这些值将是相当棘手的工作。

* 为此,Vue为这些被多个组件频繁使用的值提供了一个统一管理的工具——VueX。

// 使用

* 在具有VueX的Vue项目中,我们只需要把这些值定义在VueX中,即可在整个Vue项目的组件中使用。
* 存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,例如状态对象必须是纯粹 (plain) 的

 

2、核心概念

Vuex 的核心概念由五部分组成:State (存放状态)、Getter (加工state成员给外界)、Mutation (state成员操作)、Action (异步操作)和 Module (模块化状态管理)。

3、 State   单一状态树

 state为单一状态树,在state中需要定义我们所需要管理的数组、对象、字符串等等,
 只有在这里定义了,在vue.js的组件中才能获取你定义的这个对象的状态

* 用一个对象就包含了全部的应用层级状态。
* 每个应用将仅仅包含一个 store 实例。

至此它便作为一个“唯一数据源 (SSOT)”而存在。
单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

简单理解: 把需要做状态管理的量放到这里,然后在后面的操作中去操作它(比如修改值)

3.1 怎么在 Vue 组件中获得 Vuex 状态?

由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:

// 简单的 Store 示例代码

//store 文件夹下的index.js

import Vue from 'vue'

import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({

  state: {

    //这里放全局参数
    count: 20201029

  },

  mutations: {

    //这里是set方法

  },

  getters: {

    // 这里是get方法  
  }

})

// test.vue

<template>
  <div class="test">
    {{ count }}
  </div>
</template>

<script type="text/ecmascript-6">
import store from '../store/index.js'
export default {
  name: "test",
  data() {
    return {
      // count:store.state.count
    };
  },
  computed: {
    count () {
      console.log("count-computed",store.state.count)   //20201029
      // return store.state.count
    }
  },
  mounted() {
    console.log("count-mounted",store.state.count);    //20201029
  },
};
</script>

<style>
</style>

 
* 每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。

然而,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。

* 所以 Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)):

// main.js

import Vue from 'vue'
import App from './App'
import router from './router'
import Vuex from 'vuex'
import store from './store/index.js';
Vue.use(Vuex)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,    // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  components: { App },
  template: '<App/>'
})

* 通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。

//test.vue

<template>
  <div class="test">
    {{ this.$store.state.count }}
  </div>
</template>

<script type="text/ecmascript-6">
export default {
  name: "HelloWorld",
  data() {
    return {
    };
  },
  computed: {
    count () {
      console.log("count-computed",this.$store.state.count)   //20201029
      // return this.$store.state.count
    }
  },
  mounted() {
    console.log("count-mounted",this.$store.state.count);    //20201029
  },
};
</script>

<style>
</style>

3.2 mapState 辅助函数 

mapState

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。

所以我们可以使用 mapState 辅助函数来帮助我们生成计算属性:

// 在单独构建的版本中辅助函数为 Vuex.mapState
<template>
  <div class="test">
    {{count}}
  </div>
</template>

<script type="text/ecmascript-6">
import {mapState} from 'vuex'
export default {
  name: "test",
  data() {
    return {
      
    };
  },
   computed:mapState({    
      // 箭头函数可使代码更简练
      count:state => state.count
    }),
  mounted() {
      
  },
};
</script>

<style>
</style>

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

comput:mapState([
   // 映射 this.count 为 store.state.count
   'count'
])

 3.3 对象展开运算符

// mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?
通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。
但是自从有了对象展开运算符,我们可以极大地简化写法:

computed:{
    localComputed() {/* ... */},
    //使用对象展开运算符将此对象混入到外部对象中
    ...mapState({
       // ...
     })
}

// 例如:

computed: {
    ...mapState(["count"]),
},

需要注意的是:

Vuex 的状态固然好用,但是也不要滥用:

* 使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。
* 虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。
* 如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

4、getter

getter有点类似vue.js的计算属性
当我们需要从store的state中派生出一些状态,那么我们就需要使用getter
getter会接收state作为第一个参数,而且getter的返回值会根据它的依赖被缓存起来
只有getter中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算。

4.1  * Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

// index.js

export default new Vuex.Store({

  state: {

    //这里放全局参数
    todos: [
      { id: 1, text: '文本1', done: true },
      { id: 2, text: '文本2', done: false }
    ]
    

  },
  
  // Getter 接受 state 作为其第一个参数:

  getters: {

    // 这里是get方法  
    doneTodos: state => {
      return state.todos
    }
  },

})
// test.vue

mounted() {

   // 通过属性访问: Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:
    
    console.log(this.$store.getters.doneTodos)  

    //-> [{ id: 1, text: '文本1', done: true },{ id: 2, text: '文本2', done: false}]
  }, 

4.2 Getter 也可以接受其他 getter 作为第二个参数:

// index.js

export default new Vuex.Store({

  state: {

    //这里放全局参数
    todos: [
      { id: 1, text: '文本1', done: true },
      { id: 2, text: '文本2', done: false }
    ]
    

  },

  getters: {

    // 这里是get方法  
    doneTodos: state => {
      return state.todos
    },
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length  // 2
    }
  },


})
// test.vue

  <div class="test">

    {{this.$store.getters.doneTodosCount}}

  </div>

 computed: {
     doneTodosCount () {
       return this.$store.getters.doneTodosCount
      }
  },
  mounted() {
    console.log(this.$store.getters.doneTodosCount)    // 2
  },

* 注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。

4.3   通过方法访问:

可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

// index.js

getters: {

    getTodoById:(state) => (id) =>{

      return state.todos.find(todo => todo.id === id)

    }

  },
// test.vue

<template>
  <div class="test">
    {{this.$store.getters.getTodoById(2)}}
  </div>
</template>

<script type="text/ecmascript-6">
import { mapState } from "vuex";
export default {
  name: "test",
  data() {
    return { };
  },
  computed: {}
  },
  mounted() {
    console.log(this.$store.getters.getTodoById(2))  // { id: 2, text: '文本2', done: false }
  },
};
</script>

<style>
</style>

* 注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

4.4   mapGetters 辅助函数

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}


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

...mapGetters({
  // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
}

5、 Mutation

* 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。
这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

5.1 mutation 是操作state数据的方法的集合,比如对该数据的修改、增加、删除等等。

// index.js

const store = new Vuex.Store({
  state: {
    todos: [
       { id: 1, text: '文本1', done: true },
       { id: 2, text: '文本2', done: false }
    ],
  },
  mutations: {
    increment (state) {
       changeName(state) {
      // 变更name
        state.todos[1].text = "MrBai"
    }
    }
  }
})
// test.vue

<template>
  <div class="test">
    {{this.$store.getters.getTodoById(2)}}  

  //点击按钮触发事件后
 // { id: 2, text: 'MrBai', done: false }
  
    <button @click="changeName()">修改name</button>
  </div>
</template>
<script type="text/ecmascript-6">
import { mapState } from "vuex";
export default {
  name: "test",
  data() {
    return {
    };
  },
  computed: {
     doneTodosCount () {  
       return this.$store.getters.doneTodosCount
  }
  },
  mounted() {
    // console.log(this.$store.getters.getTodoById(2))  // { id: 2, text: '文本2', done: false }
  },
  methods:{
     changeName(){
       // 注意,我们不能直接 store.mutations.increment() 来调用,Vuex 规定必须使用 store.commit 来触发对应 type 的方法:
       this.$store.commit('changeName')
     }
  }
};
</script>

<style>
</style>

5.2  提交载荷(Payload):

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)

 

//index.js

export default new Vuex.Store({

  state: {
    count:1
  },

  mutations: {

    //这里是set方法
      changeCount(state,num) {
      // 变更count
        state.count += num
    }
  },

  getters: {
    getCount:(state) =>(count) =>{
      return state.count
    }
  }

})

 

//test.vue

 <button @click="changeCount()">修改count</button>


methods:{
     changeCount(){
       this.$store.commit('changeCount',66)
     }
  }

5.3 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

mutations: {

    //这里是set方法
      changeCount(state,num) {
      // 变更count 
        state.count += num.value
    }
  },
 methods:{
     changeCount(){
       this.$store.commit('changeCount',{
          value: 10
    })
     }
  }

 5.4  对象风格的提交方式

mutations: {

    //这里是set方法
      changeCount(state,num) {
      // 变更count
        state.count += num.value
    }
  },

 

methods:{
     changeCount(){
       this.$store.commit({
          type:'changeCount',
          value: 10
    })
     }
  }

5.5 Mutation 需遵守 Vue 的响应规则

// 动态修改对象  index.js

export default new Vuex.Store({

  state: {

    //这里放全局参数
    todos: [
      { id: 1, text: '文本1', done: true },
      { id: 2, text: '文本2', done: false }
    ],
    count:1,
    people:{
      name:'MrBai',
      sex:'男'
    }

  },

  mutations: {

    //这里是set方法
      changeCount(state,num) {

        // 给 people  添加一个年龄 age: 20 属性
        Vue.set(state.people,'age',20)
        
        // 利用对象展开运算符
        state.people = { ...state.people, age: 123 }


      // 变更count
        state.count += num.value
    }
  },

  getters: {
    getPeople:(state)=>(people)=>{
      return state.people
    }
  },


})
// test.vue

<template>
  <div class="test">
    {{this.$store.getters.getTodoById(2)}}
    {{this.$store.getters.getCount()}}  
    <button @click="changeCount()">修改count</button>
  </div>
</template>

<script type="text/ecmascript-6">
import { mapState } from "vuex";
export default {
  name: "test",
  data() {
    return {
    };
  },
  mounted() {
      console.log(this.$store.getters.getPeople()) // { age:20,name:'MrBai',sex:'男'}
  },
  methods:{
     changeCount(){
       this.$store.commit({
          type:'changeCount',
          value: 10
    })
     }
  }
};
</script>

<style>
</style>

5.6 Mutation 必须是同步函数

我们之所以要通过提交 mutation 的方式来改变状态数据,是因为我们想要更明确地追踪到状态的变化。如果像下面这样异步的话:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

我们就不知道什么时候状态会发生改变,所以也就无法追踪了,所以强制规定它必须是同步函数。

store.commit('increment')
// 任何由 "increment" 导致的状态变更都应该在此刻完成。

6、Action 

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
 actions: {
    //Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,(*它与 store 实例有着相同的方法和属性,但是他们并不是同一个实例)
    //因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters

    changeActionsName (context,playload) {
      context.commit('changeName',playload)
    }
  },
// test.vue

methods: {
    changeName() {
     this.$store.dispatch("changeActionsName",18);
    },
  },

6.1  Action 就不受约束,可以在 action 内部执行 异步 操作:

// index.js

actions: {
    changeActionsName (context,playload) {
      setTimeout(() => {
        context.commit('changeName',playload)
      }, 2000)    
    }
  },

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

// index.js
 
actions: {
    changeActionsName (context,playload) {
      setTimeout(() => {
        context.commit('changeName',playload.amount)
      }, 2000) 
    }
  },
//test.vue

 methods: {
    changeName() {
     // 以载荷形式分发
     this.$store.dispatch("changeActionsName",
      {
         amount: 18
       });
    },
     
    // 以对象形式分发
      this.$store.dispatch({
        type: "changeActionsName",
        amount: 18,
      });
  },

 6.3  在组件中分发 Action

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


import { mapActions } from 'vuex';

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

      // `mapActions` 也支持载荷:
      'changeActionsName' // 将 `this.changeActionsName(amount)` 映射为 `this.$store.dispatch('changeActionsName', amount)`
    ]),
    changeName() {
      this.changeActionsName(18)

    },
  },

6.4  Action返回的Promise

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(() => {
  // ...
})

 

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

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


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

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

前往低代码交流专区

更多推荐