Vue 过滤器和 "Do not mutate vuex store state outside mutation handlers"

Vue filter and the "Do not mutate vuex store state outside mutation handlers"

我还在学习Vue,我不明白为什么会出现这个错误。

Error: [vuex] Do not mutate vuex store state outside mutation handlers.

这是我的代码:

const DATE_FORMAT = "DD-MM-YYYY";

Vue.filter("formatDate", function(value, dateFormat) {
  dateFormat = dateFormat ? dateFormat : DATE_FORMAT;
  if (value) {
    return moment(String(value)).format(dateFormat);
  }
});

Vue.component('list-component', {
  props: ['projects'],
  template: 
`
<div>
  <ul v-for="project in projects" :key="project.id">
    <li v-for="(value, key) in project" :key="key">
      {{ key }}: {{ value }}
    </li>
    <hr />
  </ul>
</div>
`
});

const store = new Vuex.Store({
  state: {
    projects: [{
      id: 1,
      name: "super project",
      startDate: "2014-09-01T08:18:28+02:00"
    }, {
      id: 2,
      name: "extra project",
      startDate: "2017-12-20T07:28:23.133+01:00"
    }, {
      id: 3,
      name: "small project",
      startDate: "2017-12-20T07:28:23.133+01:00"
    }]
  },
  getters: {
    allProjects: state => state.projects
  },
  strict: true
});

new Vue({
  el: '#app',
  computed: {
    formattedProjects() {
      var project = store.getters.allProjects;
      project.forEach(project => {
        project.startDate = this.$options.filters.formatDate(project.startDate);
      });
      return project;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.js"></script>
<script src="https://unpkg.com/vuex@3.0.1/dist/vuex.js"></script>
<script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
<div id="app">
  <list-component :projects="formattedProjects"></list-component>
</div>

https://jsfiddle.net/9sn78n1h/2/

我无法在组件中使用我的过滤器,因为 属性 名称可能不同。在上面的示例中它是 startDate,但在其他数据中它可能是 addDateendDate 等。 在 vuex 商店中,我需要原样格式的 startDate,我不想改变它。 我不确定像 allProjectsWithFormatedDate 这样在 vuex 中写另一个 getter 是一个好习惯,但即便如此,它仍然对 mutating store 大喊大叫。

所以我在我的计算方法 formattedProjects() 中创建了一个局部变量 project,但我不知道为什么在更改它的值后会出现此错误。

有人可以帮助我理解它并展示解决这个问题的正确方法吗?

我知道我可以将 strict 模式设置为 false 来消除这个错误(我应该在生产中),但我相信有更好的方法来解决这个问题。

很简单,因为您直接修改状态而不使用 dispatchmutations

有关详细信息,请参阅 this 页面。

在上面的示例中,您可以添加如下内容:

mutations: {
  updateProject (state, obj) {
    // Use state, get the project then set startDate.
    // You can use obj.projectId and obj.startDate
  }
}

然后在循环中进行更改:

this.$store.commit('updateProject', {
  projectId: project.id, 
  startDate: this.$options.filters.formatDate(project.startDate)
})

如果您将其添加到您的 Vue 实例中,它将允许您在没有警告的情况下更新状态。

根据您所在州的大小和应用的复杂程度,您可以考虑改用 modules

根据您的评论,如果您不尝试更新商店,只需 return 新对象中的数据:

formattedProjects() {
  var project = store.getters.allProjects;
  var clone = []
  project.forEach(project => {
    clone.push({
      ...project,
      startData: this.$options.filters.formatDate(project.startDate)
    })
  });
  return clone;
}

这将 return 一个 new 对象并且不会改变存储值。 return 数据可能有更短的方法,但在我测试时使用 .map 引起了问题,但你应该能够理解......

注意:另一个想法是遍历组件上的状态并在组件内进行过滤HTML - 不确定为什么需要这样做它与计算道具。

您已更新示例:

const DATE_FORMAT = "DD-MM-YYYY";

Vue.filter("formatDate", function(value, dateFormat) {
  dateFormat = dateFormat ? dateFormat : DATE_FORMAT;
  if (value) {
    return moment(String(value)).format(dateFormat);
  }
});

Vue.component('list-component', {
  props: ['projects'],
  template: 
`
<div>
  <ul v-for="project in projects" :key="project.id">
    <li v-for="(value, key) in project" :key="key">
      {{ key }}: {{ value }}
    </li>
    <hr />
  </ul>
</div>
`
});

const store = new Vuex.Store({
  state: {
    projects: [{
      id: 1,
      name: "super project",
      startDate: "2014-09-01T08:18:28+02:00"
    }, {
      id: 2,
      name: "extra project",
      startDate: "2017-12-20T07:28:23.133+01:00"
    }, {
      id: 3,
      name: "small project",
      startDate: "2017-12-20T07:28:23.133+01:00"
    }]
  },
  getters: {
    allProjects: state => state.projects
  },
  strict: true
});

new Vue({
  el: '#app',
  computed: {
    formattedProjects() {
      var project = store.getters.allProjects;
      var clone = []
      project.forEach(project => {
        clone.push({
          ...project,
          startData: this.$options.filters.formatDate(project.startDate)
        })
      });
      return clone;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.js"></script>
<script src="https://unpkg.com/vuex@3.0.1/dist/vuex.js"></script>
<script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
<div id="app">
  <list-component :projects="formattedProjects"></list-component>
</div>

问题出在错误消息中。 Vuex 有单向流,这点你必须要注意。对状态的任何更改都必须仅在突变部分进行。所以我们错误地改变了下面一行中的对象。

project.startDate = this.$options.filters.formatDate(project.startDate);

这将 add/update(改变)商店商品 project 的开始日期 属性。这是问题的根本原因。我认为 Vuex 必须有更好的方法来处理这个问题。如果你想在你的计算 属性 本身中处理它,它可以像这样完成。

formattedProjects() {
  var clonedProject = []
  var project = store.getters.allProjects;
  project.forEach(project => {
    clonedProject.push({
      ...project,
      startData: this.$options.filters.formatDate(project.startDate)
    })
  });
  return clonedProject;
}

这里是link到fiddle