Vuex是什么?
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex
也集成到 Vue
的官方调试工具 devtools extension
,提供了诸如零配置的 time-travel
调试、状态快照导入导出等高级调试功能。
什么是“状态管理模式”?
让我们从一个简单的 Vue 计数应用开始:
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})
这个状态自管理应用包含以下几个部分:
state,驱动应用的数据源;
view,以声明方式将 state 映射到视图;
actions,响应在 view 上的用户输入导致的状态变化。
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
1.多个视图依赖于同一个状态。
2.来自不同视图的行为需要变更同一个状态。
对于问题一:传参的方式对于多层嵌套的组件将会非常的繁琐,并且对于兄弟组件之间的状态传递无能为力,对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此 我们为什么不把组件的共享状态抽取出来,以一个全局单例模式进行管理呢?
在这种模式下,我们的组件树构成了一个巨大的“视图” 不管在树的哪一个位置,任何组件都能够获取状态或者触发行为。
每一个Vuex
应用的核心就是store
(仓库)store
基本上就是一个容器,它包含着应用中大部分的状态,Vuex
和单纯的全局对象有以下两点不同:
1.`Vuex`的状态存储是响应式的,当`Vuex`组件从`store`中状态的时候,若`store`中的中的状态发生变化,那么相应的组件也会相应的得到高效的更新。
2.你不能直接更改`store` 里面的数据,改变`store` 中的状态的唯一途径就是显示的
提交(commit)mutation 这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
最简单的store:
// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(`Vuex`);
const store = new Vuex.store({
state:{
count:0
},
mutations:{
increment(state){
state.count++
}
}
})
现在,你可以通过 store.state
来获取状态对象,以及通过store.commit
方法触发
状态变更:
store.commit('increment')
console.log(store.state.count); // 1
再次强调 我们通过提交mutation的方式,而非直接改变 store.state.count
是因为我们想要更加明确地追踪到状态的变化。这个简单的约束能让你的意图更加明确,
这样你在阅读代码的时候更容易地解读应用内部的状态改变,此外,这样也能让我们有机会去实现一些记录每次状态的改变,保存状态快照的调试工具,有了它,我们甚至可以实现如时间穿梭般的调试体验。。
由于store
中的状态是响应式的,在组件中调用store
的状态简单到仅需要在计算属性中返回即可,触发变化也仅仅是在组件中methods
中提交mutation
。
State:单一状态树
Vuex
使用单一状态树-——是的,用一个对象就包含了全部的应用层级状态,至此它便作为一个“唯一数据源”而存在,这也意味着,每一个应用将仅仅包含一个store
实例。
在Vue组件中获得Vuex
状态:
那么我们如何在Vue组件中展示状态呢?由于Vuex
的状态存储是响应式的,从store
实例中读取状态的最简单的方法就是在计算属性中返回某一个状态。
const Counter = {
template: `<div>{{ count }}</div>`,
computed:{
count(){
return store.state.count
}
}
}
每当store.state.count
变化的时候,都会重新请求计算属性,并且触发
更新相关联的DOM.
然而,这种模式导致组件依赖全局状态单例,在模块化的构建系统中,在每个需要使用的
state 的组件中需要频繁地导入,并且在测试组件时候需要模拟状态。
Vuex
通过 store
选项 ,提供了一种机制将状态从根组件注入到每一个子组件中(需要调用Vue.use(Vuex))
const app = new Vue({
el: '#app',
// 把 `store` 对象提供给 “`store`” 选项,这可以把 `store` 的实例注入所有的子组件
`store`,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
通过在根实例中注册store
选项,该store
实例会注入到跟组件下的所有
子组件且子组件能通过this.$store访问到,让我们更新一下Counter的实现:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState 辅助函数:
当一个组件需要获取多个状态的时候,将这个状态都声明为计算属性有些重复和冗余,为了解决这个问题,我们可以使用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
的子节点名称相同的时候,我们也可以给mapState
传一个字符串数组。
computed: mapState({
// 映射this.count 为 `store`.state.count;
'count'
})
mapState
函数返回的是一个对象我们如何将它与局部计算属性混合使用呢? 通常我们需要使用一个工具函数将多个对象和并为一个,使得我们可以将最终对象传给computed
属性 ,但是自从有了对象的展开运算,我们极大的简化书写。
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
组件仍然保有局部状态:
使用Vuex
并不意味着你需要将所有的状态都放入Vuex
,虽然将所有的状态
放到Vuex
会使得状态变化更加显式和易于调试,但是也会使得代码变得冗长和不直观,如果有些状态严格属于单个组价,最好还是作为组件的局部状态。