关于vuex,其实17年大概3 4 月份或许更早,在喻导那里就学习了相关概念,当时学习热情不高,约等于没有,那个时候vue是个什么都不太清楚,只想好好学习jquery,这一年多跌跌撞撞的学过来,从拖延症中摆脱出来再看vuex的官方文档,结合自己平时碰到的问题,才有一种它确实是顺势而生的感觉,以下是我作为一个前端菜鸟现阶段第一次认真看文档然后对vuex的理解,先记录一下吧。
Vuex 是什么?
官方文档上写的是:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
我对这里的理解是:数据管理,比如一个网站,你的作为用户来说,你的名字/名称和头像是一个公共数据,会在网站的很多个页面/组件用到,既代码里的很多组件里用到这一条数据,如果你没有一个“状态管理器”,那么你在每个页面都要向接口请求一次数据,再给页面上的相应数据赋值,这样就很麻烦。
文档上对这种情况的解释:
当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
1.多个视图依赖于同一状态。
2.来自不同视图的行为需要变更同一状态。
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。
State
而在vuex里,你可以将数据放在一个store(仓库)的state里,要在某个组件里用的话直接拿就行了
如果你有一个数据xx要在很多个组件中使用,那么先在index.html同级的地方新建一个store文件夹,再在里面建一个index.js文件
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 告诉 vue “使用” vuex
Vue.use(Vuex)
// 创建一个对象来保存应用启动时的初始状态
// 需要维护的状态
const store = new Vuex.Store({
state: {
// 放置初始状态 app启动的时候的全局的初始值
data: {"xx":"我是vuex的第一个数据","id":100}
}
})
// 整合初始状态和变更函数,我们就得到了我们所需的 store
// 至此,这个 store 就可以连接到我们的应用中
export default store
注:在计算属性(computed)中返回某个状态
// 你创建的 Counter 组件需要读取xx数据
const Counter = {
template: `{{ count }}`,
computed: {
count () {
return store.state.data.xx
}
}
}
每当 store.state.xx变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
为了避免频繁导入state,你可以在根组件(app.vue)中将state注入到每一个子组件中,
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
components: { Counter },
template: `
`
})
并且在main.js里加上
import store from './../store/index'
new Vue({
el: '#app',
router,
store,//加上
components: { App },
template: ' '
})
Getter
计算属性 ,这个很好理解,如果你某一个组件里需要获取state里的数据并且需要过滤一下才能展示,可以用上这个,官网例子
store里的index.js
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
}
})
你的某个组件
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
同样的,如果多个组件展示该数据都需要过滤,Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Getter 接受 state 作为其第一个参数:
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)
}
}
})
并通过
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
访问
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,也就是说如果你想更改state里的数据,你就需要提交mutation,每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
官网例子
store里的index.js
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) { //increment就是上面提到的事件类型,并且在这个函数里进行状态更改
// 变更状态
state.count++
}
}
})
在你的某个组件里调用
store.commit('increment') //提交mutation,更改state数据
至于官方完档里写的关于 “不能直接调用一个 mutation handler”,这里我的理解是为了使用的时候能多样的进行数据变更吧。
就像一个简单的关注功能,你点击关注的话
store.commit('increment') //在type为increment的函数里让关注的数据加一
如果你点击取消关注
store.commit('decrement') //在type为decrement的函数里让关注的数据减一
这样你也可以在vue的调试devtools里更直观的看到数据变化,从而调试你的代码。
当然这是最简单的使用场景。
你可以向 store.commit 传入额外的参数实现更复杂的逻辑,即 mutation 的 载荷(payload),具体参考官方文档,传送门:https://vuex.vuejs.org/zh/guide/mutations.html
Action
这个其实现在看还没有很理解,先说一下对他浅浅的理解吧:
因为mutation的改变是同步的,当涉及异步操作(比如要修改一个表单里的数据,点击提交修改的时候必须先把数据传给后端,成功之后再来修改state里的数据),状态则不会改变。
所以这时候需要action来配合使用,用action来提交mutation从而修改state(状态),这里需要注意:
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 方法触发:
在需要触发action的地方
this.$store.dispatch('increment')//触发action提交type为increment的mutation从而改变状态
一个 store.dispatch 在不同模块中可以触发多个 action 函数,这是一个action:
actions: {
actionA ({ commit }) { //这个Promise执行完成之后提交type为someMutation的mutation
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
你可以:
store.dispatch('actionA').then(() => { //在actionA完成后dosomething
// ...dosomething
})
在另外一个 action 中也可以:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
触发actionB的话,触发actionA=>执行A中的Promise=>提交type为someMutation的mutation =>A完成=>提交type为someOtherMutation的mutation =>B完成。
(感觉就是在一个回调里放另一个回调吧)
这次认真的看文档就这些感受了
其他:文档上还有一些别的什么比如:
mapState,mapGetters,mapMutations,mapActions这些辅助函数,省去一些代码量,写出来更简洁
Module的概念,当应用变得非常复杂时,store 对象就有可能变得相当臃肿。Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
等等其他一些更详细的知识点要根据自己上手情况再结合文档仔细琢磨一下
ps:如果有大佬看到这篇文章并发现有什么理解不正确的地方欢迎指正。
官方文档再次传送门:https://vuex.vuejs.org/zh/