【问题标题】:Vuejs : How to pass an object as prop and have the component update sub-objectsVuejs:如何将对象作为道具传递并让组件更新子对象
【发布时间】:2018-08-10 21:14:43
【问题描述】:

我正在尝试创建一个组件,该组件接受一个对象作为道具,并且可以修改该对象的不同属性并将值返回给父级,使用同步或发出事件。该示例不起作用,但它只是为了演示我想要实现的目标。

这是我正在努力实现的目标:

Vue.component('child', {
  template: '#child',
  
  //The child has a prop named 'value'. v-model will automatically bind to this prop
  props: ['value'],
  methods: {
    updateValue: function (value) {
      //Not sure if both fields should call the same updateValue method that returns the complete object, or if they should be separate
      this.$emit('input', value);
    }
  }
});

new Vue({
  el: '#app',
  data: {
    parentObject: {value1: "1st Value", value2: "2nd value"}
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<div id="app">
  <p>Parent value: {{parentObject}}</p>
  <child v-model="parentObject"></child>
</div>

<template id="child">
   <input type="text" v-bind:value.value1="value" v-on:input="updateValue($event.target.value)">
   <input type="text" v-bind:value.value2="value" v-on:input="updateValue($event.target.value)">
</template>

【问题讨论】:

    标签: vue.js vuejs2


    【解决方案1】:

    您不应该修改作为道具传入的对象。相反,您应该在子组件中创建一个新的数据属性,并使用 prop 对象的副本对其进行初始化。

    然后,我将为子组件中的每个输入使用v-model,并为内部值添加一个深度观察器,当内部值发生变化时,它会发出update

    Vue.component('child', {
      template: '#child',
      props: ['value'],
      data() {
        return { val: {...this.value} };
      },
      watch: {
        val: {
          deep: true,
          handler(value) {
            this.$emit('input', value);
          }
        }
      }
    });
    
    new Vue({
      el: '#app',
      data: {
        parentObject: {value1: "1st Value", value2: "2nd value"}
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
    
    <div id="app">
      <p>Parent value: {{parentObject}}</p>
      <child v-model="parentObject"></child>
    </div>
    
    <template id="child">
      <div>
        <input type="text" v-model="val.value1">
        <input type="text" v-model="val.value2">
      </div>
    </template>

    我在示例 ({...this.value}) 中制作了道具的浅表副本,因为该对象没有任何嵌套属性。如果不是这种情况,您可能需要进行深层复制 (JSON.parse(JSON.stringify(this.value)))。

    【讨论】:

    • 您好,代码看起来很棒,我尝试实现它,但是一旦我将手表部件添加到组件中,它会在加载时立即崩溃。给了我这个错误,到目前为止我还没有找到解决方案,但我仍在寻找:Uncaught TypeError: Cannot assign to read only property 'watch' of object '#'
    • 因此,您在帖子中分享的代码与您现在尝试运行的代码之间肯定存在一些差异。
    • 我制作了一个新的 jsfiddle,最大限度地保真了我的真实代码,它工作正常。我怀疑错误源于 vue 实例之外,因为它是从一堆 vanillaJS 代码引导而来的。也许那里的某些东西阻止了我以某种方式使用手表组件。我可能不得不在没有手表的情况下让它工作。继承人 jsfiddle :jsfiddle.net/6zs7fssc 但没有太多可显示的,因为它不会崩溃。
    • 您链接的小提琴中的示例不起作用......而且它与您帖子中的代码不同。你确定你不只是在某个地方犯了语法错误吗?
    • 太棒了!如果这个答案是您问题的最佳解决方案,您应该接受它。这样未来的读者就可以知道什么是有效的。
    【解决方案2】:

    #app 中,应该是parentObject,而不是parentValue

    在 child 中,您有两个 inpyt,但您必须有一个根元素。在下面的示例中,我为组件创建了一个 &lt;div&gt; 根元素。

    要更新父级,请发出事件。这种方法不会修改父级在子级中的属性,因此不会破坏单向数据流。

    Vue.component('child', {
      template: '#child',
      
      //The child has a prop named 'value'. v-model will automatically bind to this prop
      props: ['value']
    });
    
    new Vue({
      el: '#app',
      data: {
        parentObject: {value1: "1st Value", value2: "2nd value"}
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
    
    <div id="app">
      <p>Parent value: {{parentObject}}</p>
      <child v-model="parentObject"></child>
    </div>
    
    <template id="child">
    <div>
       <input type="text" v-bind:value="value.value1" v-on:input="$emit('input', {value1: $event.target.value, value2: value.value2})">
       <input type="text" v-bind:value="value.value2" v-on:input="$emit('input', {value1: value.value1, value2: $event.target.value})">
    </div>
    </template>

    关于&lt;input&gt;s:您可以将每个绑定到父value 的属性。然后,在编辑时,发出一个仅修改该属性 (v-on:input="$emit('input', {value1: $event.target.value, value2: value.value2})) 并保留另一个值的事件。父级随之更新。

    如果属性比较多,可以在第二个input中替换,例如:

    $emit('input', {value1: value.value1, value2: $event.target.value})
    

    $emit('input', Object.assign({}, value, {value2: $event.target.value}))
    


    使用方法而不是直接从模板发出

    我保留了之前的演示,因为它更直接且更易于理解(对原始代码的更改更少),但更紧凑的方法是使用方法(例如updateValue)并在模板中重用它:

    Vue.component('child', {
      template: '#child',
      
      //The child has a prop named 'value'. v-model will automatically bind to this prop
      props: ['value'],
      methods: {
        updateValue: function(propertyName, propertyValue) {
          this.$emit('input', Object.assign({}, this.value, {[propertyName]: propertyValue}))
        }
      }
    });
    
    new Vue({
      el: '#app',
      data: {
        parentObject: {value1: "1st Value", value2: "2nd value"}
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
    
    <div id="app">
      <p>Parent value: {{parentObject}}</p>
      <child v-model="parentObject"></child>
    </div>
    
    <template id="child">
    <div>
      <input type="text" v-bind:value="value.value1" v-on:input="updateValue('value1', $event.target.value)">
      <input type="text" v-bind:value="value.value2" v-on:input="updateValue('value2', $event.target.value)">
    </div>
    </template>

    如您所见,上面的演示已经使用了Object.assign(),这意味着它将处理无限数量的属性。

    【讨论】:

    • 在实现你的最后一个例子时,令我惊讶的是,我使用简单的 v-model 语法让它工作,没有自定义更新方法。我是在破坏数据流还是在做违法的事情?如果没有,这似乎要简单得多。这是我如何实现它的 jsfiddle:jsfiddle.net/bj39aqud/8
    【解决方案3】:

    这是不可能的。有一个封闭的issue 要求它。这是我找到的最短的方法:

    脚本

    Vue.component('child', {
      template: `
        <div>
            <input :value="parent.foo" @change="$emit('update:parent', $event.target.value)" />
        </div>
      `,
      props: {
        parent: {
          type: Object
        }
      }
    })
    
    new Vue({
      el: '#app',
      data: () =>  ({
        parent: {foo: 'a', bar: 'b'}
      }),
    
      methods: {
        mutate (value) {
            this.parent.foo = value
        }
       }
    })
    

    模板

    <div id="app">
      {{parent}}
      <child :parent="parent" @update:parent="mutate"></child>
    </div>
    

    https://jsfiddle.net/5k4ptmqg/475/

    【讨论】:

    • 如果你的数据是 dog: {size:'small', color:'brown'},你将如何将 dog 对象传递给孩子,并在其任何 prop 更改时更新它?我在 JSFiddle 中尝试过,但无法正常工作。
    • 我明白了。在其他答案之后,这是我目前如何在我的项目中使用它:jsfiddle.net/bj39aqud/10 如果 v-model 被证明还不够,我会使用像 @acdcjunior 提到的 updateValue 方法
    【解决方案4】:
    <TradeTableItem v-for="(debtReserve, index) in debtReserves" :key="debtReserve.id" :debtReserve="debtReserve" :market="market" :id="'order_by_' + index"></TradeTableItem>
    

    在上面的步骤中,我们为每一行生成了 id。

    在 TradeTableItem(我们正在填充的模板,表格行)中,将 id 写为 :id="this.id" 其中 this.id 是 props 的一部分。

    希望对你有帮助

    【讨论】:

      【解决方案5】:

      不使用props 的另一种方法是访问子组件中的组件属性$attrs

      在父母中

      <child v-model="parentObject" />
      

      而在child中,v-model生成的value属性可以在模板中访问为

      $attrs.value.value1
      

      这是反应式的。

      演示HERE

      【讨论】:

        猜你喜欢
        • 2017-12-23
        • 2020-01-18
        • 2018-12-05
        • 1970-01-01
        • 2019-01-22
        • 1970-01-01
        • 2022-01-07
        • 2019-05-17
        • 2019-12-30
        相关资源
        最近更新 更多