【问题标题】:vuejs update parent data from child componentvuejs 从子组件更新父数据
【发布时间】:2017-04-16 08:58:51
【问题描述】:

我开始使用 vuejs (2.0)。 我构建了一个包含一个组件的简单页面。 该页面有一个带有数据的 Vue 实例。 在该页面上,我注册并将组件添加到 html。 该组件有一个input[type=text]。我希望该值反映在父级(主 Vue 实例)上。

如何正确更新组件的父数据? 从父级传递绑定的道具并不好,并向控制台抛出一些警告。他们的文档中有一些东西,但它不起作用。

【问题讨论】:

标签: javascript vue.js vue-component


【解决方案1】:

2021 年答案 - Vue 2.3+

简短回答:只需在父级中添加.sync 修饰符并将数据作为道具传递给子级:

    // PARENT:
    data () {
    return {
      formData: {
        members: [] //<- we wanna pass this one down to children and add/remove from the child component
      }
    }

   // PARENT TEMPLATE:
   <!-- ADD MEMBERS -->
  <add-members :members.sync="formData.members" />

嵌套子组件:AddMembers.vue

export default {
  name: 'AddMembers',
  props: ['members'],
  methods: {
    addMember () {
      this.members.push(new Member()) // <-- you can play and reactivity will work (in the parent)  
    },
    removeMember (index) {
      console.log('remove', index, this.members.length < 1)
      this.members.splice(index, 1)
    }
  }
}

长话短说:实际上子组件的更改正在被 $emitted 并更新父组件的 formData.members[]

来源:Mauro Perez at medium

【讨论】:

    【解决方案2】:

    还有另一种将数据更改从子节点传递到父节点的方法,它使用provide-inject 方法。父组件为子组件“提供”数据或方法,然后将该数据或方法“注入”到子组件中 - 但它也可用于触发父组件中的方法并传递参数。
    当有一个恰好嵌入到多个其他组件中的子组件时,这种方法特别有用。此外,在大型项目中必须注意不要丢失provideinject 用法的概述。

    父(顶级)组件示例App.vue 使用provide 来访问它的方法updateParentValue(如果提供了方法而不是数据,provide 的形式为一种方法):

    <template>
      <h2>App.vue, parentValue is: <em>{{ parentValue }}</em></h2>
      <ChildComponent1 />
    </template>
    
    <script>
    import ChildComponent1 from "./components/ChildComponent1.vue";
    
    export default {
      data() {
        return {
          parentValue: "",
        };
      },
      components: {
        ChildComponent1,
      },
      provide() {
        return {
          updateParent: this.updateParentValue,
        };
      },
      methods: {
        updateParentValue($value) {
          this.parentValue = $value;
        },
      },
    };
    </script>
    

    本例中组件Component4.vue在“底部”,即App.vue包含Component1,Component1包含Component2...直到Component4实际利用inject获取访问权限然后调用父方法并传递参数$value(这里只是一个随机数):

    <template>
      <div>
        <h2>ChildComponent4.vue</h2>
        <button @click="updateParent(Math.random())">
          Update parent value in App.vue
        </button>
      </div>
    </template>
    
    <script>
    export default {
      inject: ["updateParent"],
    };
    </script>
    

    整个示例可用here
    Vue.js documentation

    【讨论】:

      【解决方案3】:

      简介

      我正在寻找在 vue3 中从父级向子级(以及返回)发送数据(我知道问题是关于 vue2,但当时在 SO 上没有对 vue3 的引用)。

      下面是工作样板结果,纯“html + js”,没有打包程序、模块等,我有一些警告,解释了。

      注意事项:

      1. 插入子行
        <component-a :foo="bar" @newfooevent="bar = $event"></component-a>`
        
      • 我使用简写:foo="bar"parent.bar 绑定到child.foo,与v-bind:foo="bar" 相同。它通过 props 将数据从父级传递给子级。

      • 警告:事件监听器应仅放置在子组件标签中!

        那是@newfooevent="bar = $event" 部分。

        您无法在 &lt;div id="app"&gt; 或父级内部的任何其他位置捕获信号。

        不过,这是宇宙的父母一方,在这里您可以访问所有父母的数据并从孩子的信号中提取数据来处理它。

      1. 您可以创建应用程序,并在其后定义组件(app.component("component-a", ...) 部分。

        警告:组件的前向声明不需要,例如C/C++ 中的函数。您可以创建使用该组件的应用程序,然后定义该组件。我浪费了很多时间来寻找以某种方式声明它的方法 - 没必要。

      2. 在这里您可以找到一个很好的 v-model 用法示例,以及我用来解决问题的代码:https://javascript.plainenglish.io/vue-3-custom-events-d2f310fe34c9

      例子

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <title>App</title>
          <meta charset="utf-8" />
          <script src="https://unpkg.com/vue@next"></script>
        </head>
        <body>
          <div id="app">
            <component-a :foo="bar" @newfooevent="bar = $event"></component-a>
            <p>Parent copy of `bar`: {{ bar }}</p>
            <button @click="bar=''">Clear</button>
          </div>
      
          <script>
            const app = Vue.createApp({
              data() {
                return {
                  bar: "bar start value"
                };
              }
            });      
      
            app.component("component-a", {
              props: {
                foo: String
              },
              template: `
                <input 
                  type="text"
                  :value="foo"
                  @input="$emit('newfooevent', $event.target.value)">
              `
            });      
      
            app.mount("#app");
          </script>
        </body>
      </html>

      【讨论】:

        【解决方案4】:

        在父组件中-->

        data : function(){
                    return {
                        siteEntered : false, 
                    };
                },
        

        在子组件中 -->

        this.$parent.$data.siteEntered = true;

        【讨论】:

          【解决方案5】:

          在孩子身上

           <input
                      type="number"
                      class="form-control"
                      id="phoneNumber"
                      placeholder
                      v-model="contact_number"
                      v-on:input="(event) => this.$emit('phoneNumber', event.target.value)"
              />
          
          data(){
              return {
                contact_number : this.contact_number_props
              }
            },
            props : ['contact_number_props']
          

          在父母中

          <contact-component v-on:phoneNumber="eventPhoneNumber" :contact_number_props="contact_number"></contact-component>
          
          
           methods : {
               eventPhoneNumber (value) {
                this.contact_number = value
              }
          

          【讨论】:

            【解决方案6】:

            我认为这样可以解决问题:

            @change="$emit(variable)"

            【讨论】:

              【解决方案7】:

              他的示例将告诉您如何在提交按钮上将输入值传递给父级。

              首先将 eventBus 定义为新的 Vue。

              //main.js
              import Vue from 'vue';
              export const eventBus = new Vue();
              
              Pass your input value via Emit.
              //Sender Page
              import { eventBus } from "../main";
              methods: {
              //passing data via eventbus
                  resetSegmentbtn: function(InputValue) {
                      eventBus.$emit("resetAllSegment", InputValue);
                  }
              }
              
              //Receiver Page
              import { eventBus } from "../main";
              
              created() {
                   eventBus.$on("resetAllSegment", data => {
                       console.log(data);//fetching data
                  });
              }
              

              【讨论】:

                【解决方案8】:

                我不知道为什么,但我刚刚成功地使用数据作为对象更新了父数据,:set & computed

                父.vue

                <!-- check inventory status - component -->
                    <CheckInventory :inventory="inventory"></CheckInventory>
                
                data() {
                            return {
                                inventory: {
                                    status: null
                                },
                            }
                        },
                

                Child.vue

                <div :set="checkInventory">
                
                props: ['inventory'],
                
                computed: {
                            checkInventory() {
                
                                this.inventory.status = "Out of stock";
                                return this.inventory.status;
                
                            },
                        }
                

                【讨论】:

                  【解决方案9】:

                  另一种方法是将 setter 的引用从父组件作为 prop 传递给子组件,类似于他们在 React 中的做法。 假设您在父组件上有一个方法 updateValue 来更新值,您可以像这样实例化子组件:&lt;child :updateValue="updateValue"&gt;&lt;/child&gt;。然后在child上你会有一个对应的prop:props: {updateValue: Function},并且在模板中输入变化时调用方法:&lt;input @input="updateValue($event.target.value)"&gt;

                  【讨论】:

                    【解决方案10】:

                    正确的方法是$emit() an event in the child component that the main Vue instance listens for

                    // Child.js
                    Vue.component('child', {
                      methods: {
                        notifyParent: function() {
                          this.$emit('my-event', 42);
                        }
                      }
                    });
                    
                    // Parent.js
                    Vue.component('parent', {
                      template: '<child v-on:my-event="onEvent($event)"></child>',
                      methods: {
                        onEvent: function(ev) {
                          v; // 42
                        }
                      }
                    });
                    

                    【讨论】:

                      【解决方案11】:

                      在子组件中:

                      this.$emit('eventname', this.variable)
                      

                      在父组件中:

                      <component @eventname="updateparent"></component>
                      
                      methods: {
                          updateparent(variable) {
                              this.parentvariable = variable
                          }
                      }
                      

                      【讨论】:

                      • 这个例子简直让我大吃一惊。你不知道我在来到这里之前经历了多少教程......
                      • @Sarvar Nishonboev - 当 updateparent 方法更新父变量时,它是否“自动”将其作为道具传回给子变量?
                      • @veritas - 不,在这种情况下不是,因为子组件没有接收父变量作为道具。如果是这样,那么它会。
                      • 这个例子比其他例子最棒。
                      • 最难的是简单化。太感谢了!为什么其他长答案让它变得如此复杂。
                      【解决方案12】:

                      更简单的方法是使用this.$emit

                      Father.vue

                      <template>
                        <div>
                          <h1>{{ message }}</h1>
                          <child v-on:listenerChild="listenerChild"/>
                        </div>
                      </template>
                      
                      <script>
                      import Child from "./Child";
                      export default {
                        name: "Father",
                        data() {
                          return {
                            message: "Where are you, my Child?"
                          };
                        },
                        components: {
                          Child
                        },
                        methods: {
                          listenerChild(reply) {
                            this.message = reply;
                          }
                        }
                      };
                      </script>
                      

                      Child.vue

                      <template>
                        <div>
                          <button @click="replyDaddy">Reply Daddy</button>
                        </div>
                      </template>
                      
                      <script>
                      export default {
                        name: "Child",
                        methods: {
                          replyDaddy() {
                            this.$emit("listenerChild", "I'm here my Daddy!");
                          }
                        }
                      };
                      </script>
                      

                      我的完整示例:https://codesandbox.io/s/update-parent-property-ufj4b

                      【讨论】:

                      • 这是最好最简单的一个,你节省了我的时间,非常感谢
                      【解决方案13】:

                      子组件

                      使用this.$emit('event_name') 向父组件发送事件。

                      父组件

                      为了在父组件中监听该事件,我们执行v-on:event_name 并且我们希望在该事件上执行的方法 (ex. handleChange) 发生

                      完成:)

                      【讨论】:

                        【解决方案14】:

                        我同意上述事件发射和 v-model 的答案。然而,我想我会发布我发现的关于具有多个表单元素的组件的内容,这些元素想要返回到它们的父级,因为这似乎是谷歌返回的第一批文章之一。

                        我知道问题指定了单个输入,但这似乎是最接近的匹配项,并且可能会使用类似的 vue 组件为人们节省一些时间。另外,还没有人提到.sync 修饰符。

                        据我所知,v-model 解决方案仅适用于返回其父级的一个输入。我花了一些时间寻找它,但 Vue (2.3.0) 文档确实展示了如何将发送到组件中的多个道具同步回父级(当然是通过发射)。

                        它被恰当地称为.sync 修饰符。

                        documentation 是这样说的:

                        在某些情况下,我们可能需要对道具进行“双向绑定”。 不幸的是,真正的双向绑定会产生维护问题, 因为子组件可以在没有源的情况下改变父组件 这种突变在父母和孩子身上都很明显。

                        这就是为什么我们建议以 update:myPropName。例如,在一个假设的组件中, title prop,我们可以传达分配新值的意图 与:

                        this.$emit('update:title', newTitle)
                        

                        然后家长可以听 该事件并更新本地数据属性(如果需要)。为了 示例:

                        <text-document   
                         v-bind:title="doc.title"  
                         v-on:update:title="doc.title = $event"
                        ></text-document>
                        

                        为方便起见,我们使用 .sync 修饰符为该模式提供简写:

                        <text-document v-bind:title.sync="doc.title"></text-document>
                        

                        您还可以通过一个对象发送一次同步多个。查看documentation here

                        【讨论】:

                        • 这就是我要找的。非常感谢。
                        • 这是截至 2020 年最好、最新的解决方案。非常感谢!
                        【解决方案15】:

                        双向绑定在 Vue 2.0 中已被弃用,以支持使用更加事件驱动的架构。一般来说,孩子不应该改变它的道具。相反,它应该$emit 事件并让父级响应这些事件。

                        在您的特定情况下,您可以使用带有v-model 的自定义组件。这是一种特殊的语法,允许接近双向绑定,但实际上是上述事件驱动架构的简写。你可以在这里阅读 -> https://vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events

                        这是一个简单的例子:

                        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) {
                              this.$emit('input', value);
                            }
                          }
                        });
                        
                        new Vue({
                          el: '#app',
                          data: {
                            parentValue: 'hello'
                          }
                        });
                        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
                        
                        <div id="app">
                          <p>Parent value: {{parentValue}}</p>
                          <child v-model="parentValue"></child>
                        </div>
                        
                        <template id="child">
                           <input type="text" v-bind:value="value" v-on:input="updateValue($event.target.value)">
                        </template>

                        文档声明

                        <custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input>
                        

                        等价于

                        <custom-input v-model="something"></custom-input>
                        

                        这就是为什么孩子身上的道具需要命名为value,为什么孩子需要$emit一个名为input的事件。

                        【讨论】:

                        • 首先感谢您的回复。您能否扩展或更好地指向有关“输入”事件的文档?这似乎是一个内置事件。
                        • 我已经添加了一个说明,并使指向文档的链接更加明显。
                        • 我省略了组件和创建的函数的道具“值”,它仍然有效。你能解释一下你为什么使用它吗?
                        • 如果不添加 prop,那么在第一次更改之前,它将是 undefined。请参阅我已注释掉 props: ['value'] 的这个小提琴。注意初始值是undefined,而不是hellojsfiddle.net/asemahle/8Lrkfxj6。在第一次更改之后,Vue 动态地为组件添加了一个 value 属性,所以它可以工作了。
                        • 没有违规。父级将value 绑定到子级。这意味着子组件上的 value 属性将匹配父组件上的内容。孩子只读取这个值,从不直接更新它。当用户写文本时,孩子会发出一个具有最新值的事件(方法updateValue)。为了响应该事件更新value。反过来,这会导致子组件上的 value 属性被更新。
                        【解决方案16】:

                        来自documentation

                        在 Vue.js 中,父子组件关系可以概括为 props down,events up。父级通过 props 向下传递数据给子级,子级通过事件向父级发送消息。让我们看看他们接下来是如何工作的。

                        How to pass props

                        以下是将 props 传递给子元素的代码:

                        <div>
                          <input v-model="parentMsg">
                          <br>
                          <child v-bind:my-message="parentMsg"></child>
                        </div>
                        

                        How to emit event

                        HTML:

                        <div id="counter-event-example">
                          <p>{{ total }}</p>
                          <button-counter v-on:increment="incrementTotal"></button-counter>
                          <button-counter v-on:increment="incrementTotal"></button-counter>
                        </div>
                        

                        JS:

                        Vue.component('button-counter', {
                          template: '<button v-on:click="increment">{{ counter }}</button>',
                          data: function () {
                            return {
                              counter: 0
                            }
                          },
                          methods: {
                            increment: function () {
                              this.counter += 1
                              this.$emit('increment')
                            }
                          },
                        })
                        new Vue({
                          el: '#counter-event-example',
                          data: {
                            total: 0
                          },
                          methods: {
                            incrementTotal: function () {
                              this.total += 1
                            }
                          }
                        })
                        

                        【讨论】:

                        • 如果函数“increment”在父组件中,我想从子组件触发它怎么办?
                        • 抓住概念的瞬间,虽然已经被hacky copy paste使用了好几次......
                        • 我将打印该图形并将其贴在我的头上。谢谢!
                        • 在这个例子中,我们不应该在根组件中定义一个事件监听器吗?类似于:```mounted() { this.on('increment', () => { this.incrementTotal(); }); } ```
                        【解决方案17】:

                        也可以将 props 作为 Object 或 Array 传递。在这种情况下,数据将被双向绑定:

                        (这个在话题末尾注明:https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow

                        Vue.component('child', {
                          template: '#child',
                          props: {post: Object},
                          methods: {
                            updateValue: function () {
                              this.$emit('changed');
                            }
                          }
                        });
                        
                        new Vue({
                          el: '#app',
                          data: {
                            post: {msg: 'hello'},
                            changed: false
                          },
                          methods: {
                            saveChanges() {
                                this.changed = true;
                            }
                          }
                        });
                        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
                        
                        <div id="app">
                          <p>Parent value: {{post.msg}}</p>
                          <p v-if="changed == true">Parent msg: Data been changed - received signal from child!</p>
                          <child :post="post" v-on:changed="saveChanges"></child>
                        </div>
                        
                        <template id="child">
                           <input type="text" v-model="post.msg" v-on:input="updateValue()">
                        </template>

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2018-07-20
                          • 2017-08-03
                          • 1970-01-01
                          • 2017-12-31
                          • 2019-02-17
                          • 2017-03-17
                          相关资源
                          最近更新 更多