"Simulate" vuex 中的突变

"Simulate" mutations in vuex

import { remoteSettings } from 'somewhere';

const store = {
    state: {
        view: {
            foo: true
        }
    },
    mutations: {
        toggleFoo(state) {
            state.view.foo = !state.view.foo;
        }
    },
    actions: {
        async toggleFoo({ state, commit }) {
            commit('toggleFoo');
            await remoteSettings.save(state);
        }
    }
};

假设我有一个像这样的简单商店。 toggleFoo 动作应用突变,然后通过异步调用保存新状态。但是,如果 remoteSettings.save() 调用失败,则说明我在商店中的本地设置与远程设置不同步。我真正想在这个行动中实现的是这样的:

async toggleFoo({ state, commit }) {
    const newState = simulateCommit('toggleFoo');
    await remoteSettings.save(newState);
    commit('toggleFoo');
}

我想在不实际提交的情况下获得新状态。如果远程调用成功,那么我将实际更新商店。如果没有,它将保持原样。

实现此目的的最佳方法是什么(无需实际复制变异函数中的逻辑)?也许 "undo"?我不确定。

这样做的一种方法是:(感谢 @Bert 纠正错误)

  1. 在提交变更之前使用 const oldState = state; 存储旧状态。

  2. 将异步调用包装在 try-catch 块中。

  3. 如果 remoteSettings 失败,它将执行到 catch 块。

  4. 在 catch 块中提交突变以重置状态。

示例:

const store = {
  state: {
    view: {
      foo: true
    }
  },
  mutations: {
    toggleFoo(state) {
      state.view.foo = !state.view.foo;
    },
    resetState(state, oldState){
      //state = oldState; do not do this

       //use store's instance method replaceState method to replace rootState
        //see :   https://vuex.vuejs.org/en/api.html
      this.replaceState(oldState)
    }
  },
  actions: {
    async toggleFoo({ state, commit }) {
      const oldState =  JSON.parse(JSON.stringify(state));  //making a deep copy of the state object
      commit('toggleFoo');
      try {
        await remoteSettings.save(newState);
        //commit('toggleFoo'); no need to call this since mutation already commited
      } catch(err) {
        //remoteSettings failed
        commit('resetState', oldState)
      }
    }

  }
};

从@VamsiKrishna 借用代码(谢谢),我建议一个替代方案。在我看来,您想将更改发送到服务器,并在成功时更新本地状态。这是一个工作示例。

为防止重复逻辑,将更改抽象为一个函数。

console.clear()

const remoteSettings = {
  save(state){
    return new Promise((resolve, reject) => setTimeout(() => reject("Server rejected the update!"), 1000))
  }
}

function updateFoo(state){
  state.view.foo = !state.view.foo
}

const store = new Vuex.Store({
  state: {
    view: {
      foo: true
    }
  },
  mutations: {
    toggleFoo(state) {
      updateFoo(state)
    },
  },
  actions: {
    async toggleFoo({ state, commit }) {
      // Make a copy of the state. This simply uses JSON stringify/parse
      // but any technique/library for deep copy will do. Honestly, I don't
      // think you would be sending the *entire* state, but rather only
      // what you want to change
      const oldState = JSON.parse(JSON.stringify(state))
      // update the copy
      updateFoo(oldState)
      try {
        // Attempt to save
        await remoteSettings.save(oldState);
        // Only commit locally if the server OKs the change
        commit('toggleFoo');
      } catch(err) {
        // Otherwise, notify the user the change wasn't allowed
        console.log("Notify the user in some way that the update failed", err)
      }
    }
  }
})

new Vue({
  el: "#app",
  store,
  computed:{
    foo(){
      return this.$store.state.view.foo
    }
  },
  mounted(){
    setTimeout(() => this.$store.dispatch("toggleFoo"), 1000)
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<div id="app">
  <h4>This value never changes, because the server rejects the change</h4>
  {{foo}}
</div>