在使用Vue的一些小型应用的时候,组件之前的通信场景场景较为简单,状态管理基本在可控范围之内,
父子组件之间的通信可以使用props
来传递,默认为单向绑定,在vue 1.x
版本中可以添加sync
实现双向绑定。
<!-- 双向绑定 -->
<child :msg.sync="parentMsg"></child>
但是在vue 2
中删除了sync
属性,因为当你props
传递的链路过长时,万一数据发生了错误,定位问题将是一件很麻烦的事。如果子组件需要向父组件传递数据,可以通过$on
将父组件的事件绑定到子组件,在子组件中通过$emit
来触发$on
绑定的父组件事件。
同样的,非父子组件通信也使用以上方式。
var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
// ...
})
一、Vuex是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex
借鉴 Flux、Redux、和 The Elm Architecture的基本思想,使用单一组件树来管理共用状态,无论哪个位置的状态需要改变,都会获取状态并发出行为。
二、DEMO
这是一个官方给出的最基本的 Vuex 记数应用示例。
三、基本概念
在讲解Vuex
之前,先了解下Vuex
最基本的概念。
Vuex
分成四个部分:
- State:单一状态树
- Getters:状态获取
- Mutations:触发同步事件
- Actions:提交mutation,可以包含异步操作
正如上图所示,Vuex的数据总是“单向流动”。
- 用户访问页面并触发action
- action提交mutation事件
- mutation事件更改state状态
- 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
接受两个参数,state
与getters
,我们可以在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
更改 Vuex
的 store
中的状态的唯一方法是提交 mutation
。mutation
不能直接调用,而要通过相应的 type 调用相应的store.commit
方法:
store.commit('increment')
mutation
接受两个参数state
和payload
(载荷)。
通过执行回调函数修改state
的状态,可以向store.commit
传入额外的参数payload
,payload
可以是一个对象,这样可以包含多个字段并且记录的 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.getter
和context.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')
})
}
}