相关单词
store 仓库
state 状态
mutation 变更
shared state 共享的状态
Notify actions 通知改变
Backend 后端
dispatch 分发
内容
- Vue 应用中原始数据对象的实际来源 - 当访问数据对象时,一个 Vue 实例只是简单的代理访问。所以,如果你有一处需要被多个实例间共享的状态,可以简单地通过维护一份数据来实现共享。——共享数据
const sourceOfTruth = {}
const vmA = new Vue({
data: sourceOfTruth
})
const vmB = new Vue({
data: sourceOfTruth
})
这就是为什么不用全局变量而用vuex的原因:
现在当 sourceOfTruth 发生变化,vmA 和 vmB 都将自动的更新引用它们的视图。子组件们的每个实例也会通过 this.$root.$data 去访问。现在我们有了唯一的数据来源,但是,调试将会变为噩梦。任何时间,我们应用中的任何部分,在任何数据改变后,都不会留下变更过的记录。
- 所有 store 中 state 的改变,都放置在 store 自身的 action 中去管理。这种集中式状态管理能够被更容易地理解哪种类型的 mutation 将会发生,以及它们是如何被触发。当错误出现时,我们现在也会有一个 log 记录 bug 之前发生了什么。——这就是为什么不用全局变量而用vuex的原因。
3.
注意:你不应该在 action 中 替换原始的状态对象 - 组件和 store, 需要引用同一个共享对象,mutation 才能够被观察
我的理解图:仓库里(store实例)管理着许多共享的状态,组件触发更新,然后通知仓库改变对应状态。
- 接着我们继续延伸约定,组件不允许直接修改属于 store 实例的 state,而应执行 action 来分发 (dispatch) 事件通知 store 去改变,我们最终达成了 Flux 架构。——各自分工明确
这样约定的好处是,我们能够记录所有 store 中发生的 state 改变,同时实现能做到记录变更 (mutation)、保存状态快照、历史回滚/时光旅行的先进的调试工具。——这就是为什么使用vuex
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
Vuex 和单纯的全局对象有以下两点不同
a. vuex
的状态存储是响应式的。当 Vue 组件从 store
中读取状态的时候,若 store
中的状态发生变化,那么相应的组件也会相应地得到高效更新。
b. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。
state
单一状态树
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段。
在 Vue 组件中获得 Vuex 状态
在计算属性中返回某个状态:那么我们如何在 Vue 组件中展示状态呢?由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态。
// 创建一个 Counter 组件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
但存在的问题:在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入。
解决办法:
通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。(需调用 Vue.use(Vuex))
new Vue({
router,
store,// 在根实例中注册 store 选项
}).$mount('#app')
mapState 辅助函数
computed:mapState([
'name',
'age',
'count' // 映射 this.count 为 this.$store.state.count
])
对象展开运算符
mapState
函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。现在有了对象展开运算符可以完成~
computed:{
name:function(){/* ... */},
age(){// age: function(){}
/* ... */
},
// 使用对象展开运算符将此对象混入到外部对象中
...mapState([
'count'
])
}
对象展开运算符:
let x = 1,
y = 2,
z = {a: 3, b: 4};
let n = {x,y,...z};// n={x: 1, y: 2, a: 3, b: 4}
组件仍然保有局部状态
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
Getter
例子:有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数。
mutation
mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {// 类型为 `increment`
increment (state) {
// 变更状态
state.count++
}
}
})
你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment
的 mutation
时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type
调用 store.commit
方法:
store.commit('increment')
mutation 提交方式
提交载荷的提交方式(Payload)
你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)——在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读。
store.commit('increment',10);
store.commit('increment',{amount:10});
对象风格的提交方式(我觉得更直观理解)
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
store.commit({
type: 'increment',
amount: 10
})
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
使用常量替代 Mutation 事件类型
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Mutation 必须是同步函数
mapMutations
在组件中提交 Mutation
Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作
注意:context 对象不是 store 实例本身,context只是与 store 实例具有相同方法和属性的对象。
ES6 的 参数解构 来简化代码
const store = {
actions: {
// 与 store 实例具有相同方法和属性的 context 对象
increment(context) {
context.commit('increment',{amount:10})
},
// 或es6参数解构简化代码:var {name} = {name:1,age:2};name;// 1
increment({commit}) {
//取参数对象context里的commit属性,即 commit = context.commit
commit('increment',{amount:10})
}
}
}
分发 Action
Action 通过 store.dispatch
方法触发:
store.dispatch('increment')
在 action 内部执行异步操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
Module
例子全(我的)
// mutation-types.js 使用常量替代 Mutation 事件类型
export const SOME_MUTATION = 'SOME_MUTATION';
// store.js
import Vuex from 'vuex';
import {SOME_MUTATION} from './mutation-types';
const store = {
state: {
count: 0,
itemDetail:[
{
topic_id:10
},
{
topic_id:30
}
]
},
getters: {
downTodos:(state)=> {
return state.itemDetail.filter(item=> {
return item.topic_id > 10
})
},
downTodosCount:(state,getters)=> {
return getters.downTodos.length;
}
},
mutations: {
// 类似事件注册:当触发一个类型为`increment`的 mutation 时,调用此函数。
// mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
increment(state, payload) {
state.count += payload.amount;
},
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION](state) {
}
},
actions: {
// 与 store 实例具有相同方法和属性的 context 对象
increment(context) {
context.commit('increment',{amount:10})
},
// 或es6参数解构简化代码:var {name} = {name:1,age:2};name;// 1
// var {commit} = {commit:function(){},state:{},getters:{},mutations:{}} = context (类似store)
increment({commit,state},data) {
//取参数对象context里的commit属性,即 commit = context.commit
commit('increment',{amount:data.amount})
}
}
}
组件中使用
// 调用一个 mutation handler
store.commit('increment',{amount:10})
// 或 对象风格的提交方式
store.commit({
type:'increment',
acount: 10
})
store.commit(SOME_MUTATION,{})
我的小结
用dispatch分发action(可以包含任意异步操作),用commit提交mutation(必须是同步函数),将调用mutations的handler,再(执行回调函数)改变状态。