如何在 Vuex 突变中设置参数和参数

How to set up parameters and arguments in Vuex mutations

我正在使用 Vue.js、Vuex 和 Firebase 构建一个待办事项列表应用程序。该应用程序似乎可以正常工作,因为 Store 文件成功地管理了输入的待办事项(进出 firestore)的检索和呈现。但是,我对在 Vuex 中设置参数仍然有疑问。突变中的 REMOVE_TODO 函数(参见 store.js)似乎需要两个参数,即使 "id" 是函数实际代码中引用的唯一参数。换句话说,如果我取出初始参数(在本例中为 "state"),那么控制台将 return 一个错误,内容为:"Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: a custom Object object"。我的问题是:如果 "id" 参数是函数中实际使用的唯一参数,为什么这个 REMOVE_TODO 函数需要有两个参数才能正常运行?为什么需要另一个参数?下面是我的代码。谢谢!

app.vue

<template>
  <div id="app" class="container">
    <input class="form-control" :value="newTodo" @change="getTodo" placeholder="I need to...">
    <button class="btn btn-primary" @click="addTodo">Add New Post</button>
    <ul class="list-group">
        <li class="list-group-item" v-for="todo in this.$store.getters.getTodos" :key="todo.id">
            {{todo.title}}
            <div class="btn-group">
                <button type="button" @click="remove(todo.id)" class="btn btn-default btn-sm">
                <span class="glyphicon glyphicon-remove-circle"></span> Remove
                </button>
            </div>
        </li>
    </ul>
  </div>
</template>
<script>
export default {
  beforeCreate: function() {
    this.$store.dispatch('setTodo')
  },
  methods: {
    getTodo(event) {
      this.$store.dispatch('getTodo', event.target.value)
    },
    addTodo() {
      this.$store.dispatch('addTodo')
      this.$store.dispatch('clearTodo')
    },
    remove(id){
      this.$store.dispatch('removeTodo', id)
    }
  },
  computed: {
    newTodo() {
      return this.$store.getters.newTodo
    },
    todos(){
      return this.$store.getters.todos
    }
  }
}
</script>
<style>
body {
  font-family: Helvetica, sans-serif;
}
li {
  margin: 10px;
}
</style>

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import db from '../firebase'

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    todos: [],
    newTodo: '',
    errors: ''
  },
  mutations: { //syncronous, committed
    GET_TODO: (state, todo) => {
      state.newTodo = todo
    },
    ADD_TODO: state => {
      db.collection('items').add({
        title: state.newTodo,
        created_at: Date.now(),
      }).then(function(){
        console.log('Document successfully added')
      })
      .catch((error) => {
        this.errors = error
      })
    },
    REMOVE_TODO: (state, id) => {
       if (id) {
         db.collection("items").doc(id).delete().then(function() {
           console.log('Document successfully deleted')
         })
         .catch((error) => {
           this.errors = error
         })
       } else {
         this.errors = 'Invalid ID'
       }
    },
    CLEAR_TODO: state => {
      state.newTodo = ''
    },
    SET_TODO: state => {
      let todos = []
      db.collection('items').orderBy('created_at').onSnapshot((snapshot) => {
        todos = []
        snapshot.forEach((doc) => {
          todos.push({ id: doc.id, title: doc.data().title })
        })
        state.todos = todos
      })
    }
  },
  actions: { //asyncronous, dispatched
    getTodo: (context, todo) => {
      context.commit('GET_TODO', todo)
    },
    addTodo: context => {
      context.commit('ADD_TODO')
    },
    removeTodo: (context, id) => {
      context.commit('REMOVE_TODO', id)
    },
    clearTodo: context => {
      context.commit('CLEAR_TODO')
    },
    setTodo: context => {
      context.commit('SET_TODO')
    }
  },
  getters: {
    newTodo: state => state.newTodo,
    getTodos: state => {
      return state.todos
    }
  }
})

这与 Vuex 没有任何关系,只是 JavaScript 函数调用的工作方式。参数按位置而不是名称传递。

你有 REMOVE_TODO: (state, id) => { 但名称 stateid 只有在函数内部才真正有意义。在函数之外,从调用者的角度来看,这些名称并不重要。它可以很容易地成为 REMOVE_TODO: (a, b) => {.

当 Vuex 调用 mutation 时,它将传递 state 对象作为第一个参数,payload 作为第二个参数。实际上它在调用:

mutations.REMOVE_TODO(state, payload)

我重申,参数的名称实际上并不重要,重要的是它们的位置。

这是 Vuex 内部的,不是你可以直接控制的。如果您自己调用该函数,则可以传递任何您想要的东西,但您没有(也不应该)。相反,您(非常正确地)调用 commit。您将所需的 payload 传递给 commit,然后 Vuex 将完成剩下的工作,调用相关的突变并将其传递给 statepayload.

将函数定义为 REMOVE_TODO: (id) => { 不会更改调用方传递的参数。第一个参数仍然是 state 对象。您已将其命名为 id 但这没有任何区别,它仍然是 state 对象。

一般来说,尝试删除第一个参数的问题是所有其他参数有效地向下排列,因此所需参数名称的位置不再与传递的参数的位置匹配。从最后删除一个参数没有这样的问题,因为其他参数不会移动位置。所以如果你只需要 state 但不需要 payload 那么你可以只删除第二个参数没问题。 API 设计者总是尝试将可选参数放在末尾,这样它们可以被删除而不会导致任何问题。

对于突变,payload 很容易是不必要的,但 state 总是需要的。突变的全部意义在于改变 state。如果你不改变 state 那么你不应该使用突变。

如您的代码中所述,actions 是异步的,而 mutations 需要是同步的。但这不是你所拥有的。目前,您的突变中有很多对 Firebase 的异步调用。所有这些都需要移入 actions。请注意,Promises 始终是异步的,因此如果您发现自己在突变中调用 then,那您就错了。唯一应该在 mutations 内的部分是当您修改 state.

您还需要避免在 Vuex 存储中使用 this。你应该发现你需要的一切都可以从传递给函数的参数中访问。目前你有 this.errors = error 和类似的突变。我假设应该是 state.errors = error.

更新:

根据评论中的要求:

SET_TODO (state, todos) {
  state.todos = todos
}
setTodo ({commit}) {
  db.collection('items').orderBy('created_at').onSnapshot(snapshot => {
    const todos = []

    snapshot.forEach(doc => {
      todos.push({ id: doc.id, title: doc.data().title })
    })

    commit('SET_TODO', todos)
  })
}

请注意,这不会尝试处理可能的竞争条件。