【问题标题】:Vue mutate prop binded by v-bind with sync modifierVue mutate prop 由 v-bind 与同步修饰符绑定
【发布时间】:2021-11-19 01:32:26
【问题描述】:

我的组件数据中有一个对象。现在,我只是使用v-bind.sync 指令将对象的所有属性作为道具绑定到子组件。我正在使用内置的update 事件从子组件更新这些道具,但我仍然在控制台中收到Avoid mutation props directly 错误。这是附加的最小示例。

父组件

<template>
  <div>
    <oslo v-bind.sync="data" />
  </div>
</template>

<script>

import Oslo from '@/components/Oslo.vue'

export default {
  components: {
    Oslo,
  },
  name: 'OsloParent',
  data() {
    return {
      data: {
        data: {
          name: 'Oslo name',
          access: 'admin'
        }
      },
    }
  },
}
</script>

子组件

<template>
  <div>
    <input type="text" v-model="name" @keyup="$emit('update:name', name)" />
    <input type="text" v-model="access" @keyup="$emit('update:access', access)" />
  </div>
</template>

<script>
export default {
  props: {
    name: String,
    access: String
  },
  name: 'Oslo',
}
</script>

这只是我为重现问题而创建的示例组件。实际的组件应该通过双向绑定处理这么多道具,这就是我使用 v-bind 指令和 sync 修饰符绑定数据的原因。这是来自控制台的 Vue 警告(最常见)。

[Vue warn]: 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: "name"

有什么建议可以改进这一点或让 Vue 针对这种特定情况发出警告?上面给出的组件可以正常工作,但是 Vue 会抛出错误。

【问题讨论】:

  • docs 我了解到,如果您在此之前使用.sync 而没有明确的属性,它会假定一个对象。所以$emit 应该设置/更新整个对象,而不仅仅是它的一个属性。您可以在子组件上拥有一个计算属性,该属性收集当前属性值和$emits 整个对象。
  • 如果您可以在您的问题中制作一个小型工作示例作为 sn-p,我可以看看您如何做到这一点。
  • 您也将组件导入为Properties,但使用Oslo。所以也许引用是错误的?
  • 对于导入,实际组件很复杂,我实际上是在这里写代码,所以只是一个错字(已更正)。对于第一条评论,上面给出的两个父子组件都完美地更新了值。我在 devtools 中检查过。
  • 您可以尝试将v-model 更改为v-bind:name 以使其将属性显示为输入值,但属性的更新仅通过$emit 事件进行。还可以查看the docs 的示例。

标签: vue.js vuejs2 two-way-binding vue-directives


【解决方案1】:

尝试如下:

Vue.component('Oslo', {
  template: `
    <div>
    <input type="text" v-model="namec" @keyup="$emit('updated', {dat:namec, id: 'name'})" />
    <input type="text" v-model="accessc" @keyup="$emit('updated', {dat:accessc, id: ''})" />
  </div>
  `,
  props: {
    name: String,
    access: String
  },
  data() {
    return {
      namec: this.name,
      accessc: this.access
    }
  }
})

new Vue({
  el: '#demo',
      data() {
        return {
          name: 'Oslo name',
          access: 'admin'
        }
      },
  methods: {
    updated(data){
      data.id === 'name' ? this.name= data.dat : this.access= data.dat
      
    }
  }
})

Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
  <div>
  {{name}} {{access}}
    <oslo @updated="updated" />
    
  </div>
</div>

【讨论】:

  • 这可能没有帮助,我已经提到我有很多道具,所以我不想写太多代码。但是,您的答案似乎是可行的解决方案。我在代码中提到的组件对于模仿问题来说是最小的。
【解决方案2】:

如果您有许多属性要从子组件监听,您可以只传递一个对象并同步它而不是单个属性。请看下面的例子:

Vue.config.productionTip = false
Vue.config.devtools = false

Vue.component('Oslo', {
  template: `
    <div>
    <input type="text" v-model="comp_name" @keyup="$emit('update:name', comp_name)" />
    <input type="text" v-model="comp_access" @keyup="$emit('update:access', comp_access)" />
  </div>
  `,
  props: {
    data: {
      name: String,
      access: String,
    }
  },
  data() {
    return {
      comp_name: this.data.name,
      comp_access: this.data.access
    }
  }
})

new Vue({
  el: '#app',
  data() {
    return {
      doc: {
        name: 'Oslo name',
        access: 'admin'
      }
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <div>
    <span>---- {{ this.doc.name }}----</span>
    <span>---- {{ this.doc.access }}----</span>
    <oslo :data="this.doc" v-bind.sync="doc" />

  </div>
</div>

【讨论】:

    【解决方案3】:

    我在您的示例中发现了两个问题,可能会导致这个问题。

    1. 直接使用v-model属性。改用v-bind 让它只显示。并使用v-on:change 处理程序触发$emit('update:propertyname', value) 并发送新值以更新对象。

    2. $emit 中发送的值似乎是空的,因此没有任何变化。请改用$event.target.value

    旁注:v-on:keyup 可能不是最好听的事件,因为输入也可以拖放。在这种情况下,听v-on:change 会更好。

    仅使用v-bind.sync 而不是v-bind:propertyName.sync 时的事件监听器注意事项:

    如果要在父组件上监听子组件的update:propertyName 事件,则必须使用.capture 修饰符。否则update 事件会被子组件上的v-on:update:propertyName 捕获,这不会冒泡到父组件。

    例如,您可以在&lt;oslo&gt; 标签上使用v-on:update:name.capture="someMethod"。并在父母的methods 中有这个someMethod。调用此方法后,将在子组件上触发事件,该事件将更新对象,从而更新属性。

    大家一起:

    let Oslo = {
      props: {
        name: String,
        access: String
      },
      name: 'Oslo',
      template: `<div>
        <input type="text" :value="name" @change="$emit('update:name', $event.target.value)" />
        <input type="text" :value="access" @change="$emit('update:access', $event.target.value)" />
      </div>`
    }
    
    new Vue({
      el: "#app",
      components: {
        Oslo,
      },
      data: {
        thedata: {
          name: 'Oslo name',
          access: 'admin'
        }
      },
      methods: {
        nameWillBeUpdated: function(v) {
          console.log('New value of name will be:', v);
          // After this, the `update:name` event handler of the
          // child component is triggered and the value will change.
        },
      },
    })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
    <div id="app">
    <span>{{this.thedata.name}} - {{this.thedata.access}}</span>
    <oslo
        v-bind.sync="thedata"
        v-on:update:name.capture="nameWillBeUpdated"
    />
    </div>

    【讨论】:

    • 谢谢。 v-bind 正在工作,但值本身就是这里的问题。通过使用$event.target.value 解决
    • 是的,但是v-model 指令也打乱了流程。无论如何,太好了,它已经解决了!
    猜你喜欢
    • 2020-02-04
    • 2020-07-22
    • 2018-09-23
    • 1970-01-01
    • 2021-04-23
    • 2017-10-01
    • 2021-12-07
    • 2020-05-05
    • 1970-01-01
    相关资源
    最近更新 更多