如何在 $emit('input') 完成更新之前修改 VueJS 中的 'value' 道具

How to modify a 'value' prop in VueJS before `$emit('input')` finishes updating it

我有一个关于创建可与 v-model 一起使用的 VueJS 组件的问题,这些组件利用基础 value 道具和 $emit('input', newVal).

props: {
  value: Array
},
methods: {
  moveIdToIndex (id, newIndex) {
    const newArrayHead = this.value
      .slice(0, newIndex)
      .filter(_id => _id !== id)
    const newArrayTail = this.value
      .slice(newIndex)
      .filter(_id => _id !== id)
    const newArray = [...newArrayHead, id, ...newArrayTail]
    return this.updateArray(newArray)
  },
  updateArray (newArray) {
    this.$emit('input', newArray)
  }
}

在上面的代码示例中,如果我快速连续进行两次修改,它们都会在 "old array"(未修改的 value 属性)上执行。

moveIdToIndex('a', 4)
moveIdToIndex('b', 2)

换句话说,我需要等待 value 通过 $emit('input') 更新,以便第二次调用 moveIdToIndex 以使用已经修改的数组。

错误的解决方案 1

一种解决方法是将 updateArray 更改为:

  updateArray (newArray) {
    return new Promise((resolve, reject) => {
      this.$emit('input', newArray)
      this.$nextTick(resolve)
    })
  }

然后像这样执行:

await moveIdToIndex('a', 4)
moveIdToIndex('b', 2)

但我不想这样做,因为我需要对一组 ID 执行此操作并将它们同时移动到不同的位置。 awaiting 会大大降低性能。

错误的解决方案 2

我发现一个更好的解决方案就是这样做:

  updateArray (newArray) {
    this.value = newArray
    this.$emit('input', newArray)
  }

那么我根本不需要等待 $emit 完成。

但是,在这种情况下,VueJS 给出了一个控制台错误:

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"

有没有人有更好的解决方案?

好的。据我了解您的用例和应用程序,这些是您的选择。

首先,不要改变道具直接在内部保存道具然后修改那个值。

props: {
  value: Array
},
data() {
  return {
    val: this.value
  }
}

如果对数组的下一次修改依赖于对数组的先前修改,则不能同时执行它们。但是你需要它发生得相当快(我假设你希望用户觉得它发生得很快)。你可以做的是对组件内部的 val 进行修改,而不是让它依赖于 prop。 val 变量仅在安装组件时初始化。这样就可以在UI中即时修改数据,让数据库在后台更新。

换句话说,您的完整解决方案如下所示:

props: {
  value: Array
},
data () {
  return {val: this.value}
},
methods: {
  moveIdToIndex (id, newIndex) {
    const newArrayHead = this.val
      .slice(0, newIndex)
      .filter(_id => _id !== id)
    const newArrayTail = this.val
      .slice(newIndex)
      .filter(_id => _id !== id)
    const newArray = [...newArrayHead, id, ...newArrayTail]
    return this.updateArray(newArray)
  },
  updateArray (newArray) {
    this.val = newArray
    this.$emit('input', newArray)
  }
}

此解决方案解决了您的问题并允许您快速连续执行 moveIdToIndex 而无需等待任何事情。

现在,如果数组在应用程序的许多地方使用,下一个最好的办法是将它移动到商店并将其用作单一事实点并更新它并使用它来更新您的组件。您的状态将不会同时快速更新,然后将更新延迟到数据库适当的时间。

  1. Emit 发消息给 parent 以更改道具。
  2. 在道具上放一个 watcher(在 child 中)并放上你的代码以在那里使用新值。

这可以防止 child 改变它不拥有的数据,并允许它避免使用 nextTick。现在您的代码是异步和反应式的,不依赖于 non-deterministic 延迟。

复制 value 怎么样?

moveIdToIndex (id, newIndex) {
    const valueCopy = [...this.value]
    const newArrayHead = this.valueCopy
      .slice(0, newIndex)
      .filter(_id => _id !== id)
    const newArrayTail = this.valueCopy
      .slice(newIndex)
      .filter(_id => _id !== id)
    const newArray = [...newArrayHead, id, ...newArrayTail]
    return this.updateArray(newArray)