突变未在 Vuex 中使用计算属性注册

Mutations not registering using computed properties in Vuex

我有一个系统使用大量表单组件根据输入的信息实时更新视图。当我第一次构建应用程序时,我主要使用 v-model、本地组件数据和 vue 保存状态来将数据保存在本地存储中,以便在他们重新加载页面时保持数据不变。

然而,当我随着程序的扩展而转移到 vuex 时,这并不是那么简单,所以我决定使用 v-model 和使用 getter 和 setter 的计算属性,这样我就不必编写更改函数了50 种不同的输入。我还意识到,您可以为一个对象创建一个计算变量,并仍然使用 v-model 来访问和更新该对象的属性,如下所示:

  <div v-for="prof in info.profs">
  <textarea v-model="prof.name" class="code-input uk-input" rows="1" cols="20"></textarea>
  <textarea v-model="prof.email" class="code-input uk-input" rows="1" cols="25"></textarea>
  <textarea v-model="prof.office" class="code-input uk-input" rows="1" cols="50"></textarea> <br>
  </div>

info: {
  get () {
    return this.$store.getters.getInfo
  },
  set (payload) {
    this.$store.commit('updateInfo', payload)
  }
},

这非常有效,商店为每个 属性 更新数据,而无需创建自己单独的计算变量,但由于某种原因,它不会显示为已提交的突变 "updateInfo" 在 vue chrome 开发工具上,当我使用 vuex 的本地存储插件(如 vuex-persistedstate 或 vuex-persist)时,它不会更改本地存储数据,直到我提交另一个结构正常的突变。现在我的解决方法是在组件中创建 属性 的本地副本,然后观察 属性 的更改并提交到商店,这让我可以再次使用组件级别的 localstorage mixin,但我感觉必须有更好的方法来做到这一点,它不涉及为信息中的每个 属性 编写更改函数或计算变量,因为这在这个应用程序中会非常冗长。

data () {
return {
  info: this.$store.getters.getInfo
 }
},
watch: {
info: function(payload){
  this.$store.commit('updateInfo', payload)
 }
},

其实这两种方式都是错误的。将 strict: true 添加到您的商店,您将看到在这两种情况下都抛出错误。

在这两种选择中,profnameemailoffice 属性都被直接修改(这违反了 Vuex 原则,该原则规定每次更改都应该通过突变发生)。

同样,不是计算 setter(第一种情况),也不是观察者(第二种情况)被触发,因为你没有修改 item,而是它的深层嵌套属性(例如name).


允许您仍然使用 mixin 的最简单的解决方案是放弃 v-model 并使用 :value@input 绑定。示例:

<textarea :value="prof.name" @input="updateProf(prof, 'name', $event)" >

请注意,它使用了一个 updateProf 方法,该方法提交突变(见下文)并进入混合。

这样所有修改都在突变中完成。最后一点,如果您发现 :value@input 的使用过于冗长,您可以创建一个 custom directive 来处理它。

JSFiddle link 或下面的演示(相同代码)。

const store = new Vuex.Store({
  strict: true,
  state: {
   info: {
     profs: [
       {name: "Alice", email: "alice@example.com", office: "NY"},
        {name: "Bob", email: "bob@example.com", office: "CA"}
      ]
    }
  },
  mutations: {
   updateProf(state, {prof, prop, value}) {
     prof[prop] = value;
    }
  },
  getters: {
    getInfo: state => {
      return state.info
    }
  }
});
const mixin = {
  computed: {
    info() {
      return this.$store.getters.getInfo
    }
  },
  methods: {
    updateProf(prof, prop, e) {
     this.$store.commit('updateProf', {prof, prop, value: e.target.value})
    }
 }
}
new Vue({
  store,
  mixins: [mixin],
  el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>

<div id="app">
  {{ info }}
  <div v-for="prof in info.profs">
    <hr>
    name: <textarea :value="prof.name" @input="updateProf(prof, 'name', $event)" class="code-input uk-input" rows="1" cols="20"></textarea> <br>
    email: <textarea :value="prof.email" @input="updateProf(prof, 'email', $event)" class="code-input uk-input" rows="1" cols="25"></textarea> <br>
    office: <textarea :value="prof.office" @input="updateProf(prof, 'office', $event)" class="code-input uk-input" rows="1" cols="50"></textarea>
  </div>
</div>


保持v-model

只是为了让没有人知道我没有说这是可能的,这是您可以继续使用 v-model 的一种方法。这个替代方案的关键点是执行深度克隆和深度相等的功能。我提供了两个 simple/naive 实现,YMMV:

JSFiddle link。演示(与fiddle相同的代码)如下:

const store = new Vuex.Store({
  strict: true,
  state: {
   info: {
     profs: [
       {name: "Alice", email: "alice@example.com", office: "NY"},
        {name: "Bob", email: "bob@example.com", office: "CA"}
      ]
    }
  },
  mutations: {
   updateInfo(state, data) {
     state.info = data
    }
  },
  getters: {
    getInfo: state => {
      return state.info
    }
  }
});

// these two functions are key here
// consider using other implementations if you have more complicated property types, like Dates
function deepClone(o) { return JSON.parse(JSON.stringify(o)); }
function deepEquals(o1, o2) { return JSON.stringify(o1) === JSON.stringify(o2) }

const mixin = {
 data() {
    return {
      info: deepClone(this.$store.getters.getInfo),
    }
  },
  computed: {
    getInfo() {
      return this.$store.getters.getInfo;
    }
  },
  watch: {
    getInfo: {
     deep: true,
     handler(newInfo) {
        if (!deepEquals(newInfo, this.info)) { // condition to prevent infinite loops
       this.info = deepClone(newInfo);
        }
     }
    },
   info: {
     deep: true,
     handler(newInfo) {
      this.$store.commit('updateInfo', deepClone(newInfo))
     }
    }
 }
}
new Vue({
  store,
  mixins: [mixin],
  el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>

<div id="app">
  {{ info }}
  <div v-for="prof in info.profs">
    <hr>
    name: <textarea v-model="prof.name"  class="code-input uk-input" rows="1" cols="20"></textarea> <br>
    email: <textarea v-model="prof.email"  class="code-input uk-input" rows="1" cols="25"></textarea> <br>
    office: <textarea v-model="prof.office"  class="code-input uk-input" rows="1" cols="50"></textarea>
  </div>
</div>