【问题标题】:"Simulate" mutations in vuex在 vuex 中“模拟”突变
【发布时间】:2018-05-24 01:43:49
【问题描述】:
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');
}

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

实现这一点的最佳方法是什么(实际上不复制突变函数中的逻辑)?也许“撤消”?我不确定。

【问题讨论】:

    标签: vue.js vuejs2 vuex


    【解决方案1】:

    这样做的一种方法是:(感谢 @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)
          }
        }
    
      }
    };
    

    【讨论】:

    • 这与典型的流程有点倒退。进行异步调用,然后提交成功。这样就无需恢复到不同的状态(这可能会导致 UI 出现奇怪的行为)。
    • @Bert 是的,这是倒退的,但他想在异步调用中发送突变状态,这就是为什么在异步调用之前提交突变。
    • 那么你应该发送和更新状态的副本。这甚至不起作用。设置oldState = state 通过引用创建一个副本,当您尝试还原时,您只需将状态设置为等于自身。 codepen.io/Kradek/pen/wPVVyO?editors=1010
    • @Bert 感谢您指出...我更新了答案。你真的很好:)
    • 还有一件事;像这样替换state state = oldState 也不起作用。您需要使用replaceState,即documented in the API。我也在上面第一个链接的 cmets 中展示了这一点。
    【解决方案2】:

    从@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>

    【讨论】:

    • 谢谢两位。我现在有这样的东西。这对我来说看起来更好,而不是应用更改然后在失败时撤消它们。我只是想知道在 vuex 中是否有针对此类情况的建议方法/最佳实践/速记,因为我最近才开始使用它。
    猜你喜欢
    • 2017-10-13
    • 2021-05-01
    • 2019-04-15
    • 2021-03-29
    • 1970-01-01
    • 2019-11-13
    • 2017-11-23
    • 2018-02-04
    • 2017-01-10
    相关资源
    最近更新 更多