[Vue 系列]之 Vuex 数据流向

1,585 阅读3分钟
原文链接: mp.weixin.qq.com

           摘要

来自摩拜前端团队的  bing

本文主要了解 Vuex 的整个数据流向及各模块之间的触发使用。

欢迎留言并分享 Vuex 使用中遇到的坑,欢迎批评指正。其他前端干货敬请关注我司微信公众号 - 《前端新视野》。

日常开发中一般我们都会接触到组件化的概念,将一些通用的模块封装成组件去复用,提高开发效率,组件中父子 组件、兄弟组件、跨级组件等组件之间的通信是我们必须要去关注的。

但当我们的组件非常复杂,嵌套层级很深的时候:

  • 父组件给子组件不得不将 props 一层一层的传递下去

  • 子组件同样也要利用 $emit 事件机制层层注册

数据最终变的难以维护

这时我们可以引入 Vuex 来管理状态(数据)。


一、Vuex 是什么

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

通俗一点的话说就是:

 Vue 应用中统一管理数据的地方。

Vuex 的特点是:

把数据单独隔离 -- 意味着它有自己的生态系统,有固定的输入和输出,

图一是整个 Vuex 的数据流向及触发过程示意:

图一

其中:

  •  Actions 作为数据的输入

  • State 作为数据的输出。

二、数据流向

2.1 Components 收集用户反馈触发 Actions

图一中 Actions 是负责处理从 Vue Components 接收到的用户行为的模块。

  • 调用其他 action  

  • 发送异步请求以及提交(commit) mutations

都是在这个模块中操作。

2.1.1 定义 Action

要点:

  1. action 函数接受一个与 store 实例具有相同方法和属性的 context 对象;

  2. 因为 action  通常是异步的,可以结合 Promise 来组合多个 action 来处理复杂的异步流程。 

   // store/index.js

  1. actions:{

  2.    // 基础的action

  3.    addTodoOne(context, todo){

  4.      if(todo.value != ""){

  5.        context.commit("ADD_TODO", todo);

  6.      }

  7.    },

  8.    // 解构赋值

  9.    addTodoTwo({ commit }, todo){

  10.      if(todo.value != ""){

  11.        commit("ADD_TODO", todo);

  12.      }

  13.    },

  14.    // 异步调用

  15.    addTodoThree({ dispatch, commit}, todo){

  16.      if(todo.value != ""){

  17.        commit('ADD_TODO', todo)

  18.        return dispatch('asyncAddTodo', todo).then(() => {

  19.          commit('ADD_TODO_ASYNC', todo)

  20.        })

  21.      }

  22.    },

  23.    asyncAddTodo ({commit}, todo) {

  24.       return new Promise((resolve, reject) => {

  25.        setTimeout(() => {

  26.          resolve()

  27.        }, 1000)

  28.      })

  29.    }

  30.  },

这里:注意一下参数的不同

context 里面有:

  • dispatch -- 调用其他 action

  • commit -- 调用 mutation

2.1.2 触发 Action

定义好的 action 怎样在 Component 中触发呢?

  1. <button type

="button" class="btn btn-success btn-lg btn-block"

  1. @click="addTodos"> 添加 </button>

  2. <script>

  3. import {mapActions} from "vuex";

  4.  export default{

  5.   ...

  6.    methods:{

  7.      // 用户行为

  8.      addTodos(){

  9.        let todo = {

  10.          value: this.value,

  11.          id: ++this.id

  12.        };

  13.        this.value = "";

  14.        // 触发Action 方式一

  15.        this.$store.dispatch("addTodo", todo);

  16.        // 触发Action 方式二 、addTodo为 mapActions 函数返回

  17.        this.addTodo(todo)

  18.        // 触发Action 方式三、add为 mapActions 函数返回

  19.        this.add(todo)

  20.      },

  21.      ...mapActions([

  22.        'addTodo',

  23.      ]),

  24.      ...mapActions({

  25.        add: 'addTodo'

  26.      })

  27.    }

  28.  }

  29. </script>

最基本的触发方式:

this.$store.dispatch("addTodo", todo);

方式二和三种中使用 '...'  扩展运算符将  mapActions 返回值混入到 methods 中。

mapActions  是 Vuex 提供的辅助函数,可以有效精简我们的代码。

mapActions 可以接受一个数组或一个对象

在使用 module 时,可以接受 module 路径跟简写对象,暂不展开,详情请阅读官方文档https://vuex.vuejs.org/zh/guide/modules.html

  1. // 调用 `this.addTodo()`方法映射为 `this.$store.dispatch('addTodo')`

  2.  ...mapActions([

  3.    'addTodo',

  4.  ]),

  5. // 调用 `this.add()` 方法映射为 `this.$store.dispatch('addTodo')`

  6.  ...mapActions({

  7.     add: 'addTodo'

  8.  })

2.2 Actions 提交(commit)Mutations,请求修改 State

2.2.1 定义 Mutation

要点:

  1. mutation 默认接受当前 state 为第一个参数; 

  2. mutation 是修改 state 的唯一途径,并且是同步 执行,区别于 action  ;

  3. 最好提前在你的 Store 中初始化好所有所需属性;

  4. 当需要在对象上添加新属性时,你应该使用 Vue.set(obj, 'newProp', 123) ,使增加的新属性在 Vue 应用中是响应式 的;

  5. 官方推荐使用大写常量 来定义 mutation ,统一管理,一目了然。

  1. // mutation-type.js

  2. export const ADD_TODO = 'ADD_TODO'

  1. // store.js

  2. import { ADD_TODO } form './mutation-type.js'

  3. const store = new Vuex.Store({

  4.  state:{ ... },

  5.  getters:{ ... },

  6.  mutations:{

  7.    [ADD_TODO](state, todo) {

  8.      state.todos.push(todo);

  9.      Vue.set(state, 'newProp', 123)

  10.    }

  11.  },

  12.  actions:{ ... },

  13.  modules:{ ... }

  14. });

2.2.2 触发 Mutation

注意: 1、Component 中触发一些简单的同步操作,可直接触发(commit)mutation  

2、若需要异步操作的则需要在 action 中触发(commit)mutation  

  1. // component 中直接触发(commit)Mutation

  2. <button class="btn btn-default btn-danger"

  3. @click="delTodo(index)"> 删除 </button>

  4. <script>

  5.  import { mapMutations } from "vuex";

  6.  export default{

  7.   ...

  8.    methods: {

  9.      delTodo(index){

  10.        // 可以向commit传入额外的参数,

  11.        // 即mutation的(payload)

  12.        this.$store.commit("DEL_TODO", index)

  13.      },

  14.      // 将 `this.DEL_TODO()` 映射为

  15.      //  `this.$store.commit('DEL_TODO')

  16.      ...mapMutations([

  17.        'DEL_TODO'

  18.      ]),

  19.      // 将 `this.testDel()` 映射为

  20.      // `this.$store.commit('DEL_TODO')

  21.      ...mapMutations({

  22.        testDel: 'DEL_TODO'

  23.      })

  24.    }

  25. }

  26. </script>

  1. // actions 中异步触发

  2. ...

  3. actions:{

  4.    // 异步调用

  5.    addTodoThree({ dispatch, commit}, todo){

  6.      if(todo.value != ""){

  7.        commit('ADD_TODO', todo)

  8.        return dispatch('asyncAddTodo', todo).then(() => {

  9.          commit('ADD_TODO_ASYNC', todo)

  10.        })

  11.      }

  12.    },

  13.    asyncAddTodo ({commit}, todo) {

  14.       return new Promise((resolve, reject) => {

  15.        setTimeout(() => {

  16.          resolve()

  17.        }, 1000)

  18.      })

  19.    }

  20.  },

方式一:在组件的事件中直接调用

this.$store.commit("DEL_TODO",index)

方式二:异步操作在 action 中完成

2.3 Mutation 同步修改 State

  1. // store.js

  2. mutations:{

  3.    [ADD_TODO](state, todo) {

  4.      state.todos.push(todo);

  5.      Vue.set(state, 'newProp', 123)

  6.    },

  7.    [ADD_TODO_ASYNC](state, todo) {

  8.      state.todos.push(todo);

  9.    },

  10.    [DEL_TODO](state, index) {

  11.      state.todos.splice(index, 1);

  12.    }

  13.  },

2.4 State 改变后重新渲染(Render)Components

2.4.1 使用 State

Component 中是怎样绑定 state 的值呢?

                            
  1. import {mapState , mapGetters} from "vuex";

  2.   export default {

  3.    name : 'app' ,

  4.    data () { ... },

  5.    computed :{

  6.       // 第一种

  7.      state1 () {

  8.         return this .$store. state.state1

  9.       },

  10.       // 第二种

  11.       // 当计算属性的名称与state的状态名称一样时,可以省写

  12.       // 映射 this.state2 为 store.state.state2

  13.       ...mapState({

  14.        state2 : state => state.state2

  15.       }),

  16.       // 第三种 同名state也可以用数组的方式省写

  17.       // 映射 this.state2 为 store.state.state2

  18.       ...mapState([

  19.         'state2'

  20.       ]),

  21. }

2.4.2 使用 Getter

如果想要从 Store 中的 state 去衍生出一些状态,可以使用 getter,相当于 state 的计算属性

可以在实例化的时候和 state 并行,定义一个 getters

  1. // store.js

  2. const store = new Vuex.Store({

  3.  state: { ... },

  4.  getters: {

  5.    count: state => {

  6.      return state.todos.length + 10;

  7.    }

  8.  }

  9. })

注意:getter count 的第二个参数也可以关注一下

组件中像下面这样调用:

从 $store.getters 里面获取

  1. // 组件中

  2. computed:{

  3.      // 第一种

  4.      todoCount () {

  5.        return this.$store.getters.count

  6.      },

  7.      // 第二种

  8.      ...mapGetters({

  9.        count: 'count'

  10.      }),

  11.      // 第三种

  12.      ...mapGetters([

  13.        'count'

  14.      ])

  15.    },

至此,一个完整的数据流向就闭环了。

总结

本文主要介绍了从用户触发开始到再次渲染更新组件结束过程中, Vuex 的数据流向及各模块的使用方式,希望对初学 Vuex 或 打算使用 Vuex 管理数据的朋友有所帮助,有任何问题或讲述不清的地方,请留言相告。


最后来个硬广:

🌟 我们出书啦,欢迎点击阅读原文试读和购买👇