前言
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
举一个简单的例子,都知道父子组件通信,那么兄弟组件通信怎么办那?可能有人想到先把子组件的数据传递到父组件中,然后兄弟组件接收父组件中的数据。诚然这样的方式确实可以实现我们的需求,可要是嵌套的过多的话,那么麻烦吗?答案是肯定的!
简单来说,VueX 可以帮助我们共享数据,和组件一样,实现复用。
Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT (opens new window))”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
安装
直接下载 / CDN 引用
https://unpkg.com/vuex 提供了基于 NPM 的 CDN 链接。以上的链接会一直指向 NPM 上发布的最新版本。也可以通过 https://unpkg.com/vuex@2.0.0
这样的方式指定特定的版本。
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@3.6.0/dist/vuex.js"></script>
在 Vue 之后引入 vuex 会进行自动安装,注意:VueX要在Vue之后引用。
NPM
npm install vuex --save
YARN
yarn add vuex
模块化引用
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
state 存放状态
new Vuex.Store({
state: {},
mutations: {},
getters:{},
actions:{},
modules:{}
})
以上就是一个简单的Store实例,通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过this.$store
访问到。通常数据都存放在state中,访问的话不能直接引用这个value,而是通过this.$store.state.value
:
<body>
<div id="app">
{{this.$store.state.msg}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@3.6.0/dist/vuex.js"></script>
<script>
const store = new Vuex.Store({
state: {
count: 999999,
msg:"mssage"
}
})
new Vue({
el: '#app',
store: store
})
</script>
</body>
在实例中注册后就可以使用了,基本和data中的数据一样,可以在任何组件中直接调用。当然不要直接使用this.$store.state.value
修改数据,因为是共享的,直接修改对项目不易维护,建议使用下面几个部分修改。
getters 加工state成员给外界
这个就和computed
有一点类似,它不是把数据直接返回,而是对数据加工后,返回一个新的数据。保持state数据不变。
<div id="app">
<p>{{this.$store.getters.format}}</p>
<button @click="add">button</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@3.6.0/dist/vuex.js"></script>
<script>
const store = new Vuex.Store({
state: {
count: 999999,
msg:"mssage"
},
getters:{
format(store){
return store.msg+"PDDDDDD!"
}
}
})
var app = new Vue({
el: '#app',
store: store,
methods: {
add(){
this.$store.commit('add',10)
}
}
})
</script>
</body>
除此之外,可以通过返回一个函数进行数据传参:
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果
mutations 操作state
这个属性和methods属性是一样的,都是定义的方法。使用流程是,在methods中通过this.$store.commit('function', 'arguments')
进行调用:
<div id="app">
{{this.$store.state.msg}}
{{this.$store.state.count}}
<button @click="add">button</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@3.6.0/dist/vuex.js"></script>
<script>
const store = new Vuex.Store({
state: {
count: 999999,
msg:"mssage"
},
mutations: {
add (state,n) {
state.count+=n
}
}
})
var app = new Vue({
el: '#app',
store: store,
methods: {
add(){
this.$store.commit('add',10)
}
}
})
</script>
commit第二个参数不光光是简单的数据类型,还可以是一个对象,这样就能进行多个数值的传递了。提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
store.commit({
type: 'increment',
amount: 10
})
还有一点比较重要:mutations 必须是同步函数,这一点主要考虑了devtools 调试工具,异步操作并不能实时更新mutations
中的数据 ,这会给我们带来麻烦。
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
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。
Action 通过 store.dispatch
方法触发:
store.dispatch('increment')
异步操作:
actions:{
aEdit(context,payload){
setTimeout(()=>{
context.commit('edit',payload)
},2000)
}
}
// 在组件中调用
this.$store.dispatch('aEdit',{age:15})
组合 Action:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
// 调用
store.dispatch('actionA').then(() => {
...
})
modules 模块化状态管理
当项目庞大,状态非常多时,可以采用模块化管理模式。Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
models:{
a:{
state:{},
getters:{},
....
}
}
组件内调用模块a的状态:
this.$store.state.a
而提交或者dispatch某个方法和以前一样,会自动执行所有模块内的对应type的方法:
this.$store.commit('editKey')
this.$store.dispatch('aEditKey')
虽然是模块化了,但是最终渲染的时候还是只有一个Store实例,这个要知道
项目结构
Vuex 不限制代码结构。但是规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
只要你遵守以上规则,如何组织代码随你便。如果 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
对于大型应用,可以把 Vuex 相关代码分割到模块中。下面是项目结构示例: