VueX 填坑指南

Bill Qiu  |  2016. 12. 20   |  阅读 7231 次
vuex 前端开发 无线开发

在我们构建一些小型应用的时候,组件之前的通信场景场景较为简单,状态管理基本在可控范围之内,

父子组件之间的通信可以使用props来传递,默认为单向绑定,在vue 1.x版本中可以添加sync实现双向绑定。

<!-- 双向绑定 -->  
<child :msg.sync="parentMsg"></child>  

但是在vue 2中删除了sync属性,因为当你props传递的链路过长时,万一数据发生了错误,定位问题将是一件很麻烦的事。如果子组件需要向父组件传递数据,可以通过$on将父组件的事件绑定到子组件,在子组件中通过$emit来触发$on绑定的父组件事件。DEMO

同样的,非父子组件通信也使用以上方式。

var bus = new Vue()

// 触发组件 A 中的事件
bus.$emit('id-selected', 1)

// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {  
  // ...
})

一、Vuex是什么?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex借鉴 FluxRedux、和 The Elm Architecture的基本思想,使用单一组件树来管理共用状态,无论哪个位置的状态需要改变,都会获取状态并发出行为。

二、DEMO

这是一个官方给出的最基本的 Vuex 记数应用示例。

三、基本概念

vuex

在讲解Vuex之前,先了解下Vuex最基本的概念。

Vuex分成四个部分:

  • State:单一状态树
  • Getters:状态获取
  • Mutations:触发同步事件
  • Actions:提交mutation,可以包含异步操作

正如上图所示,Vuex的数据总是“单向流动”。

  1. 用户访问页面并触发action
  2. action提交mutation事件
  3. mutation事件更改state状态
  4. state状态改变后更新页面(vue comptents)

四、State

之所以叫单一状态树,就是因为用一个对象包含了全部的应用层级状态。在Vue组件中如果想要获取Vuex的状态,都需要从state中获取。最简单的方式就是在计算属性中返回state的某个状

// 创建一个 Counter 组件
const Counter = {  
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

如果你想在每个子组件中都调用state,可以在根组件中注册store选项,vuex就会提供了一种机制将状态从根组件『注入』到每一个子组件中(需调用 Vue.use(Vuex))。

const app = new Vue({  
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store
});

/*子组件*/
const Counter = {  
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

Vuex 2中新增了一个mapState辅助函数,当一个组件需要获取多个状态时,可以直接在函数中声明。

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {  
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

当计算属性与state子节点相同时,可以这么写:

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

五、Getters

state中的某些状态在各个组件中都被频繁使用,如果在每个组件中都声明一次,将会变得非常繁琐。因此便有了getters来帮助我们解决这个问题,你也可以把它看做Vuex的计算属性。getters接受两个参数,stategetters,我们可以在store中定义getters

const store = new Vuex.Store({  
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)    //{ id: 1, text: '...', done: true }
    }
  }
})

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

可以在组件中轻松使用:

computed: {  
  doneTodos () {
    return this.$store.getters.doneTodos
  }
}

Vuex 2中提供了一个mapGetters辅助函数用于有多个状态需要获取的情况。

import { mapGetters } from 'vuex'

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

如果需要重命名,可以这样写:

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

六、Mutations

更改 Vuexstore 中的状态的唯一方法是提交 mutationmutation不能直接调用,而要通过相应的 type 调用相应的store.commit方法:

store.commit('increment')  

mutation接受两个参数statepayload(载荷)。

通过执行回调函数修改state的状态,可以向store.commit传入额外的参数payloadpayload可以是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读。

// ...
mutations: {  
  increment (state, payload) {
    state.count += payload.amount
  }
}

store.commit('increment', {  
  amount: 10
})

遵守Vue的响应规则

Vuex中的store是响应式的,因此当要在对象上添加新属性时,应该使用Vue.set()方法来监听,否则state的状态无法实现自动更新。

Vue.set(obj, 'newProp', 123)  

为了避免这样的情况出现,最好在store中提前初始化好所有需要使用的属性。

mutation必须是同步函数

请看下面的例子

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

当我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志,每触发一次mutation,devtools 都需要捕捉到前一状态和后一状态的快照。而在异步函数中,当mutation被触发时,回调可能还没有被调用,而且无法捕获到回调函数什么时候被调用—— 实质上任何在回调函数中进行的的状态的改变都是不可追踪的。

在组件中提交Mutations

import { mapMutations } from 'vuex'

export default {  
  // ...
  methods: {
    ...mapMutations([
      'increment' // 映射 this.increment() 为 this.$store.commit('increment')
    ]),
    ...mapMutations({
      add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
    })
  }
}

七、Actions

Action相对于mutation,有以下不同:

  • Action 提交的是 mutation,而不是直接变更状态。
  • 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提交mutations,也可以使用context.gettercontext.state来获取getter和state。有一点需要注意点是,context并不是state实例本身。

在使用commit时,常使用参数结构的方式来简化代码:

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

分发Action

store.dispatch('increment')  

为什么要使用action而不是直接分发mutation呐?因为mutation必须执行同步函数,而在Action中可以执行异步函数。与mutation类似,Actions 支持同样的载荷(payload)方式和对象方式进行分发:

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

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

在组件中可以使用以下方式分发Action

import { mapActions } from 'vuex'

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

参考文献

文档信息

分享到

   
过程产生美-如何做好项目管理