我相信,了解 Mutations 和 Actions 背后的动机可以让人们更好地判断何时使用哪些以及如何使用。在“规则”变得模糊的情况下,它还使程序员摆脱了不确定性的负担。在对它们各自的目的进行了一些推理之后,我得出的结论是,虽然使用 Actions 和 Mutations 的方法肯定有错误,但我认为没有规范的方法。
让我们首先尝试理解为什么我们会经历突变或动作。
为什么首先要通过样板文件?为什么不直接在组件中更改状态?
严格来说,您可以直接从您的组件中更改state。 state 只是一个 JavaScript 对象,没有什么神奇的东西可以恢复您对它所做的更改。
// Yes, you can!
this.$store.state['products'].push(product)
但是,这样做会将状态突变分散到各处。您无法简单地打开一个包含状态的模块,然后一目了然地查看可以对其应用什么样的操作。集中式突变解决了这个问题,尽管以一些样板为代价。
// so we go from this
this.$store.state['products'].push(product)
// to this
this.$store.commit('addProduct', {product})
...
// and in store
addProduct(state, {product}){
state.products.push(product)
}
...
我认为如果你用样板替换一些短的东西,你会希望样板也很小。因此,我认为突变是对状态的本地操作的非常薄的包装器,几乎没有业务逻辑。换句话说,mutations 主要用于像 setter 一样。
现在您已经集中了您的变更,您可以更好地了解您的状态更改,并且由于您的工具 (vue-devtools) 也知道该位置,因此可以更轻松地进行调试。还值得记住的是,许多 Vuex 的插件不直接观察状态来跟踪变化,而是依赖于突变。因此,对状态的“越界”更改对他们来说是不可见的。
那么mutations,actions到底有什么区别?
动作,如突变,也驻留在商店的模块中,可以接收state 对象。这意味着他们可以也直接对其进行变异。那么两者兼得有什么意义呢?如果我们认为突变必须保持小而简单,这意味着我们需要一种替代方法来容纳更复杂的业务逻辑。行动是做到这一点的手段。正如我们之前所建立的,vue-devtools 和插件通过 Mutations 知道变化,为了保持一致,我们应该继续在我们的操作中使用 Mutations。此外,由于动作意味着包罗万象,并且它们封装的逻辑可能是异步的,因此动作也可以从一开始就简单地设为异步。
人们经常强调动作可以是异步的,而突变通常不是。您可能决定将这种区别视为一种指示,即突变应该用于任何同步(以及任何异步操作);但是,如果您需要提交多个突变(同步),或者如果您需要使用突变中的 Getter,您会遇到一些困难,因为突变函数既不接收 Getter 也不接收 Mutations 作为参数......
...引出了一个有趣的问题。
为什么 Mutations 不接收 Getter?
我还没有找到这个问题的满意答案。我已经看到核心团队的一些解释,我认为充其量是没有实际意义的。如果我总结一下它们的用法,Getter 是用来计算(并且通常是缓存)状态的扩展。换句话说,它们基本上仍然是状态,尽管需要一些前期计算并且它们通常是只读的。这至少是鼓励使用它们的方式。
因此,防止 Mutations 直接访问 Getter 意味着现在需要以下三件事之一,如果我们需要从前者访问后者提供的某些功能:(1) Getter 提供的状态计算在某处重复可被 Mutation 访问(难闻的气味),或 (2) 计算值(或相关的 Getter 本身)作为显式参数传递给 Mutation(时髦),或 (3) Getter 的逻辑本身直接复制在 Mutation 中,没有 Getter 提供的缓存的额外好处(恶臭)。
以下是 (2) 的示例,在我遇到的大多数情况下,这似乎是“最不坏”的选项。
state:{
shoppingCart: {
products: []
}
},
getters:{
hasProduct(state){
return function(product) { ... }
}
}
actions: {
addProduct({state, getters, commit, dispatch}, {product}){
// all kinds of business logic goes here
// then pull out some computed state
const hasProduct = getters.hasProduct(product)
// and pass it to the mutation
commit('addProduct', {product, hasProduct})
}
}
mutations: {
addProduct(state, {product, hasProduct}){
if (hasProduct){
// mutate the state one way
} else {
// mutate the state another way
}
}
}
在我看来,以上内容不仅有点令人费解,而且还有些“漏洞”,因为 Action 中存在的一些代码显然是从 Mutation 的内部逻辑中渗出的。
在我看来,这是妥协的迹象。我相信允许 Mutations 自动接收 Getters 会带来一些挑战。它可以是 Vuex 本身的设计,也可以是工具(vue-devtools 等),或者是为了保持某种向后兼容性,或者是所有所述可能性的某种组合。
我不认为自己将 Getter 传递给您的 Mutations 必然表明您做错了什么。我认为它只是“修补”框架的缺点之一。