在Vuex中,同时具有“动作”和“变异”的逻辑是什么?
我了解组件无法修改状态(看起来很聪明)的逻辑,但是同时具有动作和突变似乎就像您在编写一个函数来触发另一个功能,然后改变状态一样。
“动作”和“变异”之间有什么区别,它们如何协同工作,而且,我很好奇为什么Vuex开发人员决定采用这种方式?
mutations
和actions
的vuex文档中definted为改变状态的方法。您不需要采取任何操作即可实施突变。
在Vuex中,同时具有“动作”和“变异”的逻辑是什么?
我了解组件无法修改状态(看起来很聪明)的逻辑,但是同时具有动作和突变似乎就像您在编写一个函数来触发另一个功能,然后改变状态一样。
“动作”和“变异”之间有什么区别,它们如何协同工作,而且,我很好奇为什么Vuex开发人员决定采用这种方式?
mutations
和actions
的vuex文档中definted为改变状态的方法。您不需要采取任何操作即可实施突变。
Answers:
问题1:为什么Vuejs开发人员决定采用这种方式?
回答:
问题2:“操作”和“变异”有什么区别?
首先让我们看一下官方的解释:
变异:
Vuex突变本质上是事件:每个突变都有一个名称和一个处理程序。
import Vuex from 'vuex' const store = new Vuex.Store({ state: { count: 1 }, mutations: { INCREMENT (state) { // mutate state state.count++ } } })
动作:动作只是调度突变的函数。
// the simplest action function increment (store) { store.dispatch('INCREMENT') } // a action with additional arguments // with ES2015 argument destructuring function incrementBy ({ dispatch }, amount) { dispatch('INCREMENT', amount) }
这是我对上述内容的解释:
变异是同步的,而动作可以是异步的。
换句话说:如果您的操作是同步的,则不需要执行操作,否则可以执行操作。
我相信,了解“突变和动作”背后的动机可以使人们更好地判断何时使用哪种方式。在“规则”变得模糊的情况下,这也使程序员摆脱了不确定的负担。在对它们各自的用途进行了一些推理之后,我得出的结论是,尽管使用操作和变异的方法肯定是错误的,但我认为并没有一种规范的方法。
首先让我们尝试理解为什么我们甚至经历突变或动作。
为什么首先要通过样板?为什么不直接在组件中更改状态?
严格来说,您可以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)
}
...
我认为,如果您用样板替换一些短的东西,您会希望样板也要小。因此,我认为突变是针对状态本机操作的非常薄的包装,几乎没有业务逻辑。换句话说,突变意味着像塞特犬一样经常使用。
既然您已经集中了突变,就可以更好地了解状态变化,并且由于您的工具(vue-devtools)也知道该位置,因此使调试更加容易。还需要记住的是,许多Vuex的插件并不直接监视状态来跟踪更改,而是依赖于突变。因此,对状态的“超出范围”更改对他们是不可见的。
那么
mutations
,actions
到底有什么区别呢?
动作(如突变)也驻留在商店的模块中,并且可以接收state
对象。这意味着,他们可能还直接发生变异它。那么,两者都具有什么意义呢?如果我们认为必须使变异保持小而简单,则意味着我们需要一种替代方法来容纳更复杂的业务逻辑。行动是做到这一点的手段。而且,由于我们早先已经建立了,vue-devtools和插件可以通过Mutations感知更改,因此,为了保持一致,我们应该继续从操作中使用Mutations。此外,由于动作本应包含所有内容,并且它们封装的逻辑可以是异步的,所以有意义的是,动作从一开始也将简单地变为异步的。
人们经常强调动作可以是异步的,而变异通常不是异步的。您可能会决定将这种区别视为一种指示,即对于同步的任何事物(对于异步的任何事物)都应使用突变;但是,例如,如果您需要(同步)提交多个突变,或者需要使用突变中的Getter,则您会遇到一些困难,因为突变函数既不接受Getter也不接受Mutations作为参数。
...导致一个有趣的问题。
为什么突变不接受吸气剂?
对于这个问题,我还没有找到满意的答案。我已经看到核心团队的一些解释,我认为这充其量是最好的。如果我总结一下它们的用法,则应该将Getter扩展为该状态的扩展(通常是缓存的)。换句话说,它们基本上仍然是状态,尽管需要一些预先计算,并且它们通常是只读的。至少这是鼓励它们被使用的方式。
因此,阻止Mutations直接访问Getters意味着,如果我们需要从前者访问后者提供的某些功能,则现在有三件事之一是必要的:(1)Getter提供的状态计算要么在可访问的某个地方重复进行,到Mutation(难闻的气味),或(2)将计算的值(或相关的Getter本身)作为显式变元传递给Mutation(笨拙),或(3)Getter的逻辑本身直接在Mutation中重复,而没有Getter(stench)提供的额外缓存优势。
下面是(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
}
}
}
在我看来,以上内容似乎不仅令人费解,而且有些“漏水”,因为《行动》中的某些代码显然是从“变异”的内部逻辑中渗出的。
我认为,这表明存在妥协。我相信,允许Mutations自动接收Getters会带来一些挑战。它既可以是Vuex本身的设计,也可以是工具(vue-devtools等)的设计,或者是保持一定的向后兼容性,或者是所有陈述的可能性的某种组合。
我不相信自己将Getters传递给您的Mutations必然表明您做错了事。我认为这只是“修补”框架的缺点之一。
computed
输出。它们是只读的。一种更好的查看突变的方法可能是删除if else
已有的突变。vuex文档说您可以commit
在一个动作中容纳多个。因此,假设您可以根据逻辑进行某些突变是合乎逻辑的。我将动作视为指示WHICH突变触发的一种方式。
我认为TLDR的答案是,突变旨在实现同步/交易。因此,如果您需要运行Ajax调用或执行任何其他异步代码,则需要在Action中执行该操作,然后在之后进行更改以设置新状态。
根据 docs
动作类似于突变,不同之处在于:
考虑以下代码段。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++ //Mutating the state. Must be synchronous
}
},
actions: {
increment (context) {
context.commit('increment') //Committing the mutations. Can be asynchronous.
}
}
})
动作处理程序(increment)收到一个上下文对象,该对象在商店实例上公开了相同的方法/属性集,因此您可以调用context.commit进行更改,或通过context.state和context.getters访问状态和获取方法。
免责声明-我只是刚刚开始使用vuejs,所以这只是我在推断设计意图。
时间机器调试使用状态快照,并显示操作和变异的时间线。从理论上讲,我们可以只actions
记录状态设置器和获取器以同步描述突变。但是之后:
mutations
事务但是我们可以说需要改进事务,而不是将其作为行动中的竞争条件。由于异步编程既脆弱又困难,因此动作内部的匿名突变可以更容易地重现此类错误。将以下事务日志与命名突变进行比较。
Action: FetchNewsStories
Mutation: SetFetchingNewsStories
Action: FetchNewsStories [continuation]
Mutation: DoneFetchingNewsStories([...])
对于没有命名突变的事务日志:
Action: FetchNewsStories
Mutation: state.isFetching = true;
Action: FetchNewsStories [continuation]
Mutation: state.isFetching = false;
Mutation: state.listOfStories = [...]
我希望您可以从该示例中推断出动作内部异步和匿名突变可能增加的复杂性。
https://vuex.vuejs.org/en/mutations.html
现在,假设我们正在调试应用程序,并查看devtool的变异日志。对于记录的每个突变,devtool将需要捕获状态的“之前”和“之后”快照。但是,上面的示例突变中的异步回调使这成为不可能:在提交突变时尚未调用该回调,并且devtool无法知道何时将实际调用该回调-回调中执行的任何状态突变本质上是不可追踪的!
变异:
Can update the state. (Having the Authorization to change the state).
动作:
Actions are used to tell "which mutation should be triggered"
以Redux方式
Mutations are Reducers Actions are Actions
为什么两者都??
当应用程序增长,编码和行数增加时,那时候您必须处理Actions中的逻辑而不是突变中的逻辑,因为突变是更改状态的唯一权限,因此应尽可能保持干净。
这也让我感到困惑,因此我做了一个简单的演示。
component.vue
<template>
<div id="app">
<h6>Logging with Action vs Mutation</h6>
<p>{{count}}</p>
<p>
<button @click="mutateCountWithAsyncDelay()">Mutate Count directly with delay</button>
</p>
<p>
<button @click="updateCountViaAsyncAction()">Update Count via action, but with delay</button>
</p>
<p>Note that when the mutation handles the asynchronous action, the "log" in console is broken.</p>
<p>When mutations are separated to only update data while the action handles the asynchronous business
logic, the log works the log works</p>
</div>
</template>
<script>
export default {
name: 'app',
methods: {
//WRONG
mutateCountWithAsyncDelay(){
this.$store.commit('mutateCountWithAsyncDelay');
},
//RIGHT
updateCountViaAsyncAction(){
this.$store.dispatch('updateCountAsync')
}
},
computed: {
count: function(){
return this.$store.state.count;
},
}
}
</script>
store.js
import 'es6-promise/auto'
import Vuex from 'vuex'
import Vue from 'vue';
Vue.use(Vuex);
const myStore = new Vuex.Store({
state: {
count: 0,
},
mutations: {
//The WRONG way
mutateCountWithAsyncDelay (state) {
var log1;
var log2;
//Capture Before Value
log1 = state.count;
//Simulate delay from a fetch or something
setTimeout(() => {
state.count++
}, 1000);
//Capture After Value
log2 = state.count;
//Async in mutation screws up the log
console.log(`Starting Count: ${log1}`); //NRHG
console.log(`Ending Count: ${log2}`); //NRHG
},
//The RIGHT way
mutateCount (state) {
var log1;
var log2;
//Capture Before Value
log1 = state.count;
//Mutation does nothing but update data
state.count++;
//Capture After Value
log2 = state.count;
//Changes logged correctly
console.log(`Starting Count: ${log1}`); //NRHG
console.log(`Ending Count: ${log2}`); //NRHG
}
},
actions: {
//This action performs its async work then commits the RIGHT mutation
updateCountAsync(context){
setTimeout(() => {
context.commit('mutateCount');
}, 1000);
}
},
});
export default myStore;
经过研究,我得出的结论是,突变是仅关注于更改数据以更好地分离关注点并改进更新数据前后的日志记录的约定。而动作是抽象层,处理高层逻辑,然后适当地调用变异
1.从docs:
动作类似于突变,不同之处在于:
- 动作不会改变状态,而是执行突变。
- 动作可以包含任意异步操作。
这些动作可以包含异步操作,但该突变不能。
2.我们调用突变,我们可以直接改变状态。并且我们也可以像这样通过以下方式更改状态:
actions: {
increment (store) {
// do whatever ... then change the state
store.dispatch('MUTATION_NAME')
}
}
Actions是为处理更多其他事情而设计的,我们可以在其中执行许多操作(可以使用异步操作),然后通过在其中进行调度突变来更改状态。
似乎没有必要再加上一层actions
来调用mutations
,例如:
const actions = {
logout: ({ commit }) => {
commit("setToken", null);
}
};
const mutations = {
setToken: (state, token) => {
state.token = token;
}
};
因此,如果调用actions
电话logout
,为什么不叫突变本身?
动作的整个想法是从一个动作内部调用多个变异,或者发出Ajax请求或您可以想象的任何一种异步逻辑。
我们最终可能会发出发出多个网络请求的动作,并最终调用许多不同的突变。
因此,我们尝试的东西尽可能多的复杂性,从我们Vuex.Store()
尽可能在我们actions
这离开我们mutations
,state
和getters
清洁,简单,与这类模块化,使得像Vue的图书馆和应对流行的线下降。
我已经专业使用Vuex大约3年了,这就是我想出的关于动作和突变之间的本质区别,如何从它们一起使用中受益以及如何使生活变得更艰难的方法。不好用。
Vuex的主要目标是提供一种控制应用程序行为的新模式:反应性。这个想法是将应用程序状态的流程分配到一个专门的对象:商店。它方便地提供了将组件直接连接到商店数据以方便自己使用的方法。这使您的组件可以专注于其工作:定义模板,样式和基本组件行为以呈现给用户。同时,存储处理繁重的数据负载。
但是,这不仅是这种模式的唯一优势。存储是整个应用程序的单一数据源,这一事实为在许多组件之间重用这些数据提供了巨大的潜力。这不是尝试解决跨组件通信问题的第一个模式,但它的亮点在于,它通过基本上禁止组件修改共享数据的状态来迫使您对应用程序实施非常安全的行为。 ,并强制其使用“公共端点”进行更改。
基本思想是这样的:
就是说,魔术始于我们开始以这种方式设计应用程序时。例如:
最后,我们的用户体验被认为是“活跃的”。从我们用户的角度来看,该项目已被立即删除。在大多数情况下,我们希望端点正常工作,因此这很完美。当它失败时,我们仍然可以控制应用程序的响应方式,因为我们已经成功地将前端应用程序状态的关注与实际数据分开了。
请注意,您并不总是需要商店。如果发现正在编写如下外观的商店:
export default {
state: {
orders: []
},
mutations: {
ADD_ORDER (state, order) {
state.orders.push(order)
},
DELETE_ORDER (state, orderToDelete) {
state.orders = state.orders.filter(order => order.id !== orderToDelete.id)
}
},
actions: {
addOrder ({commit}, order) {
commit('ADD_ORDER', order)
},
deleteOrder ({commit}, order) {
commit('DELETE_ORDER', order)
}
},
getters: {
orders: state => state.orders
}
}
在我看来,您似乎只是将存储用作数据存储,并且可能由于没有让它也控制应用程序响应的变量而错过了它的反应性方面。基本上,您可以并且可能应该将编写在组件中的一些代码行卸载到商店中。