为啥我的 Vue 应用程序中的 v-model 输入会在更改时触发突变错误?

Posted

技术标签:

【中文标题】为啥我的 Vue 应用程序中的 v-model 输入会在更改时触发突变错误?【英文标题】:Why are v-model inputs in my Vue app triggering mutation errors on change?为什么我的 Vue 应用程序中的 v-model 输入会在更改时触发突变错误? 【发布时间】:2020-10-23 02:21:19 【问题描述】:

我有一些带有本地数据对象的 V-Model 输入:

如果我将本地过滤器对象作为我的操作的有效负载发送,则一切正常,没有错误。 如果我首先将过滤器对象存储在 vuex 存储中,然后将其作为有效负载传递给我的操作,它也可以工作,但是如果我随后更改组件中的任何输入,我会收到 Vuex 突变错误。

为什么更改 v-model 输入会触发突变?如何将此组件过滤器对象存储在 vuex 存储中并将其用作过滤操作的有效负载?

<template>
  <div class="q-pa-md q-mb-md bg-grey-2 rounded-borders">
    <div class="row q-gutter-md items-start justify-start">
      <q-select
        :options="typeOptions"
        dense
        outlined
        transition-hide="flip-down"
        transition-show="flip-up"
        v-model="filter.type"
        style="min-width: 120px;"
      />

      <q-input dense outlined v-model="filter.startLastSeenTime" mask="date">
        <template v-slot:append>
          <q-icon name="event" class="cursor-pointer">
            <q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
              <q-date v-model="filter.startLastSeenTime" @input="() => $refs.qDateProxy.hide()" />
            </q-popup-proxy>
          </q-icon>
        </template>
      </q-input>

      <q-input dense outlined v-model="filter.endLastSeenTime" mask="date">
        <template v-slot:append>
          <q-icon name="event" class="cursor-pointer">
            <q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
              <q-date v-model="filter.endLastSeenTime" @input="() => $refs.qDateProxy.hide()" />
            </q-popup-proxy>
          </q-icon>
        </template>
      </q-input>

      <q-input
        dense
        outlined
        placeholder="Keyword search"
        type="search"
        v-model="filter.textSearch"
      >
        <template v-slot:append>
          <q-icon name="search" />
        </template>
      </q-input>

      <q-input
        dense
        outlined
        placeholder="Browser name"
        v-model="filter.browser"
      />

      <q-input
        dense
        outlined
        placeholder="Device ID"
        v-model="filter.deviceId"
      />

      <q-input
        dense
        outlined
        placeholder="Tracking ID"
        v-model="filter.trackingId"
      />

      <q-input
        dense
        outlined
        placeholder="Fingerprint"
        v-model="filter.fingerprint"
      />

      <q-input
        dense
        outlined
        placeholder="IP address"
        v-model="filter.ipAddress"
      />

      <q-input
        dense
        outlined
        placeholder="Installation ID"
        v-model="filter.installationId"
      />

      <q-input
        dense
        outlined
        placeholder="Hook name"
        v-model="filter.hookName"
      />

      <q-input
        dense
        outlined
        placeholder="Channel ID"
        v-model="filter.channelId"
      />

      <q-checkbox
        color="primary"
        class="self-center"
        dense
        label="Forensics Active"
        v-model="filter.forensicsActive"
      />

      <q-space />

      <q-btn @click="resetFilter();" label="reset" color="grey" />
      <q-btn @click="filterDevices();" label="Filter" color="primary" class="q-px-md" />
    </div>
  </div>
</template>

<script>
export default 
  name: 'Filters',
  data() 
    return 
      typeOptions: [
        'android', 'ios', 'Web',
      ],
      filter: 
        type: 'ANDROID',
        page: 0,
        forensicsActive: false,
        textSearch: '',
        browser: '',
        deviceId: '',
        trackingId: '',
        fingerprint: '',
        ipAddress: '',
        installationId: '',
        hookName: '',
        channelId: '',
        startLastSeenTime: '',
        endLastSeenTime: '',
      ,
    ;
  ,
  methods: 
    resetFilter() 
      Object.keys(this.filter).forEach((key) => 
        this.filter[key] = '';
      );
      this.filter.forensicsActive = false;
      this.filter.page = 0;
      this.$store.commit('Devices/UPDATE_ACTIVE_FILTER', this.filter);
      this.$store.dispatch('Devices/filterDevices', this.$store.getters['Devices/activeFilter']);
    ,
    filterDevices() 
      // this works also! but after the first time it runs any change to the inputs in this component throws a vuex mutation error
      // this.$store.commit('Devices/UPDATE_ACTIVE_FILTER', this.filter);
      // this.$store.dispatch('Devices/filterDevices', this.$store.getters['Devices/activeFilter']);

      // this works without errors
      this.$store.dispatch('Devices/filterDevices', this.filter);
    ,
  ,
;
</script>

状态:(Vuex 模块)

export default function () 
  return 
    activeFilter: 
      browser: '',
      channelId: '',
      deviceId: '',
      endLastSeenTime: '',
      fingerprint: '',
      forensicsActive: false,
      hookName: '',
      installationId: '',
      ipAddress: '',
      page: 0,
      startLastSeenTime: '',
      textSearch: '',
      trackingId: '',
      type: 'ANDROID',
    ,
    devicesList: ,
    deviceToView: [],
  ;


错误:

[Vue warn]: Error in callback for watcher "function ()  return this._data.$$state ": "Error: [vuex] do not mutate vuex store state outside mutation handlers."

(found in <Root>)
warn @ vue.runtime.esm.js?5593:619
logError @ vue.runtime.esm.js?5593:1884
globalHandleError @ vue.runtime.esm.js?5593:1879
handleError @ vue.runtime.esm.js?5593:1839
run @ vue.runtime.esm.js?5593:4570
update @ vue.runtime.esm.js?5593:4542
notify @ vue.runtime.esm.js?5593:730
reactiveSetter @ vue.runtime.esm.js?5593:1055
set @ vue.runtime.esm.js?5593:1077
callback @ Filters.vue?d4cd:25
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
Vue.$emit @ vue.runtime.esm.js?5593:3888
Vue.<computed> @ backend.js:1793
toggleOption @ QSelect.js?9e66:449
click @ QSelect.js?9e66:282
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
Vue.$emit @ vue.runtime.esm.js?5593:3888
Vue.<computed> @ backend.js:1793
__onClick @ QItem.js?fe67:97
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
original._wrapper @ vue.runtime.esm.js?5593:6917
vue.runtime.esm.js?5593:1888 Error: [vuex] do not mutate vuex store state outside mutation handlers.
    at assert (vuex.esm.js?94e4:94)
    at Vue.store._vm.$watch.deep (vuex.esm.js?94e4:834)
    at Watcher.run (vue.runtime.esm.js?5593:4568)
    at Watcher.update (vue.runtime.esm.js?5593:4542)
    at Dep.notify (vue.runtime.esm.js?5593:730)
    at Object.reactiveSetter [as type] (vue.runtime.esm.js?5593:1055)
    at Proxy.set (vue.runtime.esm.js?5593:1077)
    at callback (Filters.vue?d4cd:25)
    at invokeWithErrorHandling (vue.runtime.esm.js?5593:1854)
    at VueComponent.invoker (vue.runtime.esm.js?5593:2179)
logError @ vue.runtime.esm.js?5593:1888
globalHandleError @ vue.runtime.esm.js?5593:1879
handleError @ vue.runtime.esm.js?5593:1839
run @ vue.runtime.esm.js?5593:4570
update @ vue.runtime.esm.js?5593:4542
notify @ vue.runtime.esm.js?5593:730
reactiveSetter @ vue.runtime.esm.js?5593:1055
set @ vue.runtime.esm.js?5593:1077
callback @ Filters.vue?d4cd:25
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
Vue.$emit @ vue.runtime.esm.js?5593:3888
Vue.<computed> @ backend.js:1793
toggleOption @ QSelect.js?9e66:449
click @ QSelect.js?9e66:282
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
Vue.$emit @ vue.runtime.esm.js?5593:3888
Vue.<computed> @ backend.js:1793
__onClick @ QItem.js?fe67:97
invokeWithErrorHandling @ vue.runtime.esm.js?5593:1854
invoker @ vue.runtime.esm.js?5593:2179
original._wrapper @ vue.runtime.esm.js?5593:6917

我尝试了一整天我能读到的所有内容,包括 https://vuex.vuejs.org/guide/forms.html 和 lodash clone,但我仍然不明白为什么在不触发我的方法的情况下更改输入会导致发生突变。

【问题讨论】:

【参考方案1】:

只有按照in the doc 所述设置双向计算属性时,才能将v-model 指令与存储在 Vuex 中的数据一起使用。

更新

根据我对 cme​​ts 的了解,您应该将您的突变更改为以下内容:

export default 
    // ...
    state: 
        activeFilter: 
            browser: '',
            channelId: '',
            deviceId: '',
            endLastSeenTime: '',
            fingerprint: '',
            forensicsActive: false,
            hookName: '',
            installationId: '',
            ipAddress: '',
            page: 0,
            startLastSeenTime: '',
            textSearch: '',
            trackingId: '',
            type: 'ANDROID',
        
    ,
    mutations: 
        // ...
        UPDATE_ACTIVE_FILTER(state, payload) 
            Object.keys(payload).forEach(filterKey => 
                state.activeFilter[filterKey] = payload[filterKey];
            );
        
    

【讨论】:

这是我感到困惑的部分。我认为组件本地数据与 vuex 是分开的。我的意图是获取绑定到我的输入的 v-model 的本地过滤器对象,并将其作为我的操作的有效负载发送到商店。我在这里错过了什么? 确实,本地数据和vuex数据是分开的。我的解决方案是去掉本地数据,直接编辑 Vuex 数据。如果由于某些原因,您希望在组件中拥有 vuex 数据的本地副本(而不是实际使用双向计算属性),您也可以这样做,但可能会遗漏一些东西。请发布您的商店代码,以便我可以帮助您。 如果您在上面看到我的过滤器方法,它会获取本地过滤器对象并将其作为我的操作的有效负载传递。这按预期工作,没有错误。注释掉的部分做同样的事情,但它首先将过滤器对象存储在状态中,并使用 getter 作为操作的有效负载来检索它。这两个都有效。但是在 getter 用作有效负载的情况下,对 v-model 输入的任何后续触摸都会触发错误,即使一切仍按预期工作。为什么在我触发突变之前操作本地数据对象会导致错误? 我认为您的问题在于您的UPDATE_ACTIVE_FILTER 突变。当您使用本地过滤器分配过滤器属性(vuex 端)时,您基本上只需复制对该对象的引用。当它们指向同一个对象时,v-model 指令会触发此错误。如果您也需要,我可以帮助您解决此问题,只需发布​​此突变的代码即可。 导出函数 UPDATE_ACTIVE_FILTER(state, payload) state.activeFilter = payload;当您单击过滤器按钮时,本地 this.filter 对象将作为有效负载传递。然后它分派一个动作,该动作的有效负载是 state.activeFilter 的 getter。这一切都按预期工作,使用新过滤器更新状态并向 API 发出请求。但是本地过滤器对象的 v-model 更新会在触发此突变之前触发更改错误。【参考方案2】:

您不应该直接在组件中使用突变。

这是您应该使用的,而不是 this.$state.committhis.$state.dispatch

https://vuex.vuejs.org/guide/actions.html

https://vuex.vuejs.org/guide/actions.html#dispatching-actions-in-components

这是您应该使用的,而不是 this.$state.getters

https://vuex.vuejs.org/guide/getters.html

https://vuex.vuejs.org/guide/getters.html#the-mapgetters-helper

【讨论】:

组件使用commit和dispatch。数据使用 map getter 在不同的组件中呈现。该错误消息令人困惑,因为它实际上是由复制一个全新对象而不是更新当前对象的值引起的。

以上是关于为啥我的 Vue 应用程序中的 v-model 输入会在更改时触发突变错误?的主要内容,如果未能解决你的问题,请参考以下文章

Vue:为啥计算属性在商店中声明时需要 v-model 指令中的 .value 而在组件中声明时不需要

为啥 vue v-model 不适用于数组道具?

Vue .sync 仅适用于 v-model,但会出现突变错误

如何在 vue 3 脚本设置中的组件上使用 v-model

Vue:嵌套 v-for-loop 中的 v-model

html.erb 模板中的 v-model 渲染 function () [native code]