【问题标题】:How to access to a child method from the parent in vue.js如何从 vue.js 中的父级访问子方法
【发布时间】:2017-04-18 19:52:29
【问题描述】:

我有两个嵌套组件,从父级访问子方法的正确方法是什么?

this.$children[0].myMethod() 似乎可以解决问题,但它很丑,不是吗,还有什么更好的方法:

<script>
import child from './my-child'

export default {
  components: {
   child
  },
  mounted () {
    this.$children[0].myMethod()
  }
}
</script>

【问题讨论】:

  • 首先,问问自己你真的需要。如果您的所有页面状态都在商店中,那么就不需要进行父子通信。
  • @bbsimonbb 状态与事件不同。这特别是关于从父级触发子事件。你也可以通过向下游传递一个 prop 来做任何你想要使用 Vuex 的事情,但这需要子组件监视 prop/store 的变化,以便你有效地模拟 RPC 的数据变化,这在你想要的只是完全错误的时候在组件中触发一个动作。

标签: javascript vue.js


【解决方案1】:

您可以使用ref

import ChildForm from './components/ChildForm'

new Vue({
  el: '#app',
  data: {
    item: {}
  },
  template: `
  <div>
     <ChildForm :item="item" ref="form" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.$refs.form.submit()
    }
  },
  components: { ChildForm },
})

如果您不喜欢紧耦合,您可以使用@Yosvel Quintero 所示的Event Bus。下面是另一个使用事件总线的例子,通过将总线作为道具传递。

import ChildForm from './components/ChildForm'

new Vue({
  el: '#app',
  data: {
    item: {},
    bus: new Vue(),
  },
  template: `
  <div>
     <ChildForm :item="item" :bus="bus" ref="form" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.bus.$emit('submit')
    }
  },
  components: { ChildForm },
})

组件代码。

<template>
 ...
</template>

<script>
export default {
  name: 'NowForm',
  props: ['item', 'bus'],
  methods: {
    submit() {
        ...
    }
  },
  mounted() {
    this.bus.$on('submit', this.submit)
  },  
}
</script>

https://code.luasoftware.com/tutorials/vuejs/parent-call-child-component-method/

【讨论】:

  • 这是正确的答案,实际上阅读了实际问题。选择的答案实际上回答了相反的问题(如何从子组件触发父组件的方法)。
  • 这个答案正在链接的Event Bus的链接,在阅读@bbsimonbb comment之后重定向到State Management,它有点道理。
  • 值得一提的是,如果你使用this.$refs.,你不应该动态加载子组件。
  • 谢谢您,先生!你给我省了很多麻烦。我正在解决一个生产问题,并且正在拼命寻找答案!
  • this.$ref.ref 似乎返回了一个数组。所以对我来说this.$refs.ref[0].autofocus(); 工作
【解决方案2】:

VueJS 中的父子通信

假设所有后代都可以通过this.$root 访问根Vue 实例,父组件可以通过this.$children 数组访问子组件,而子组件可以通过this.$parent 访问它的父组件,您的第一直觉可能是直接访问这些组件。

VueJS 文档对此特别提出警告,原因有两个:

  • 它将父母与孩子紧密结合(反之亦然)
  • 您不能依赖父级的状态,因为它可以修改 通过子组件。

解决方法是使用Vue的自定义事件接口

Vue 实现的事件接口允许你在组件树上下通信。利用自定义事件接口,您可以访问四种方法:

  1. $on() - 允许你在你的 Vue 实例上声明一个监听器来监听事件
  2. $emit() - 允许您在同一实例(自身)上触发事件

使用$on()$emit() 的示例:

const events = new Vue({}),
    parentComponent = new Vue({
      el: '#parent',
      ready() {
        events.$on('eventGreet', () => {
          this.parentMsg = `I heard the greeting event from Child component ${++this.counter} times..`;
        });
      },
      data: {
        parentMsg: 'I am listening for an event..',
        counter: 0
      }
    }),
    childComponent = new Vue({
      el: '#child',
      methods: {
      greet: function () {
        events.$emit('eventGreet');
        this.childMsg = `I am firing greeting event ${++this.counter} times..`;
      }
    },
    data: {
      childMsg: 'I am getting ready to fire an event.',
      counter: 0
    }
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.28/vue.min.js"></script>

<div id="parent">
  <h2>Parent Component</h2>
  <p>{{parentMsg}}</p>
</div>

<div id="child">
  <h2>Child Component</h2>
  <p>{{childMsg}}</p>
  <button v-on:click="greet">Greet</button>
</div>

答案取自原帖:Communicating between components in VueJS

【讨论】:

  • 谢谢你,所以我会尝试通过事件来实现我的代码!
  • 在复制/粘贴内容时,最好也提及来源。
  • 这有助于孩子与父母的交流。但是从父母到孩子有没有类似的方法呢?例如。在我允许用户添加新孩子之前,我希望验证所有现有的孩子 - 验证逻辑在孩子中,所以我想通过所有这些并执行例如validate() 方法。
  • 这回答了与实际问题相反的问题。 Desmond Lua 的回答回答了实际问题。
  • Event bus is #1 on Chris Fritz' list of vue antipatterns。任何可以用事件和分布式状态建模的东西都可以用全局状态和双向绑定来建模,一般情况下你会做得更好。
【解决方案3】:

建议的解决方案适用于 Vue 2,但如果您最终在这里寻找 Vue 3 组合 API 解决方案,您可以在迁移时执行以下操作:

模板中的子组件,具有“doSomething”方法:

 <div class="form">                                                                                                                                                        
      <child-component ref="childComponentRef" />                                                                      
</div>  

使用 Vue 2:

this.$refs.childComponentRef.doSomething( );
       

使用 Vue 3 组合 API:

    setup( )
    {
        const childComponentRef = ref( );

        childComponentRef.value.doSomething( )

        return {
           childComponentRef
        }
     }  

【讨论】:

    【解决方案4】:

    如果你最终在这里寻找 Vue 3 script setup

    <!-- Parent -->
    <template>
        <ChildComponent ref="childComponentRef" />
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue'
    import ChildComponent from './components/ChildComponent.vue'
    
    const childComponentRef = ref()
    
    onMounted(() => {
        childComponentRef.value.doSomething()
    })
    </script>
    
    <!-- Child -->
    <script setup>
    const doSomething = () => {
        console.log('Im batman')
    }
    
    // Only available in Vue >= 3.1.3
    // No need to import
    defineExpose({
        doSomething
    })
    </script>
    

    如果您的 Vue 版本是 &lt; 3.1.3,则必须使用 setup 函数并返回 doSomething 函数才能在父组件中访问它。

    <!-- Child -->
    <script>
    import { defineComponent } from 'vue'
    
    export default defineComponent({
        setup() {
            const doSomething = () => {
                console.log('Im batman')
            }
    
            return { doSomething }
        }
    })
    </script>
    

    【讨论】:

    • 在创建ref 时应该使用InstanceType。所以const childComponentRef = ref&lt;InstanceType&lt;typeof ChildComponent&gt;&gt;()
    • @sanscheese 为什么?这里或问题中没有使用打字稿。无论如何,defineExpose() 是我错过的事情。在我将它放入我的子组件后立即工作。
    • 是的,您必须将 ref 设置为目标 dom。 ref.value 代表 dom,然后通过调用 ref.value.method() 检索子函数
    【解决方案5】:

    当您的控件渲染受v-if 影响时,Ref 和事件总线都会出现问题。所以,我决定采用更简单的方法。

    这个想法是使用数组作为队列来发送需要调用的方法到子组件。一旦组件被挂载,它将处理这个队列。它监视队列以执行新方法。

    (从 Desmond Lua 的答案中借用一些代码)

    父组件代码:

    import ChildComponent from './components/ChildComponent'
    
    new Vue({
      el: '#app',
      data: {
        item: {},
        childMethodsQueue: [],
      },
      template: `
      <div>
         <ChildComponent :item="item" :methods-queue="childMethodsQueue" />
         <button type="submit" @click.prevent="submit">Post</button>
      </div>
      `,
      methods: {
        submit() {
          this.childMethodsQueue.push({name: ChildComponent.methods.save.name, params: {}})
        }
      },
      components: { ChildComponent },
    })
    

    这是 ChildComponent 的代码

    <template>
     ...
    </template>
    
    <script>
    export default {
      name: 'ChildComponent',
      props: {
        methodsQueue: { type: Array },
      },
      watch: {
        methodsQueue: function () {
          this.processMethodsQueue()
        },
      },
      mounted() {
        this.processMethodsQueue()
      },
      methods: {
        save() {
            console.log("Child saved...")
        },
        processMethodsQueue() {
          if (!this.methodsQueue) return
          let len = this.methodsQueue.length
          for (let i = 0; i < len; i++) {
            let method = this.methodsQueue.shift()
            this[method.name](method.params)
          }
        },
      },
    }
    </script>
    

    还有很大的改进空间,比如将 processMethodsQueue 移动到 mixin...

    【讨论】:

    • 运行方法后是否应该将 childMethodsQueue 数组重置为空?
    • 我发现它应该被重置为一个空数组。还需要进行其他几项更改,但是在这里输入的时间太长,因此我发布了一个答案,其中包含使其工作所需的所有更改。谢谢你给我一个起点。
    • @McGrew 感谢您发布您的答案。我认为问题与 Vue 3 的变化有关。这段代码仍然在我们的旧代码库中工作。无论如何,我相信这些功能需要在框架中,我不高兴我以这种方式使用它。这些是 Vue.js 的改进点。
    【解决方案6】:

    我喜欢 mohghaderi 的回答,但我遇到了几个问题,所以我将使用他的示例代码来展示我需要进行的更改以使其正常工作。 (在我自己的项目中,我使用的是 Vue 3 和 Options API。)

    mohghaderi 的父组件代码以及关于我的更改的注释:

    import ChildComponent from './components/ChildComponent'
    
    new Vue({
      el: '#app',
      data: {
        item: {},
        childMethodsQueue: [],
      },
      // Note: In the template below, I added @child-methods-finished="childMethodsFinished" 
      //       as an event listener, so that we can reset the childMethodsQueue array to
      //       empty once the methods are finished.
      //       If you don't reset it, then the methods stay in there and cause problems.
      template: `
      <div>
         <ChildComponent :item="item" 
                         :methods-queue="childMethodsQueue"
                         @child-methods-finished="childMethodsFinished" />
         <button type="submit" @click.prevent="submit">Post</button>
      </div>
      `,
      methods: {
        submit() {
          this.childMethodsQueue.push({
            name: ChildComponent.methods.save.name,
            params: {}  // Note: delete the {} and put the name of your params, if you use a method that passes in params.
          })
        }
      },
      components: { ChildComponent },
    })
    

    mohghaderi 的子组件代码以及关于我的更改的注释:

    import { objectToString } from "@vue/shared"
    
    export default {
        name: 'ChildComponent',
        props: {
          methodsQueue: { type: Array },
        },
        // Note:  I had to rewrite the watch option because it would not trigger.
        //        You have to add "deep, true" for arrays and objects.
        //        The function has to be called "handler" for it to work as well.
        watch: {
          methodsQueue: {
            handler() {
              this.processMethodsQueue()
            },
            deep: true,
          }
        },
        // Note:  Remove "mounted()" function if you don't want it to run on the mounted event.
        mounted() {
          this.processMethodsQueue()
        },
        methods: {
          save() {
              console.log("Child saved...")
          }, 
          processMethodsQueue() {
            if (!this.methodsQueue) return
            let len = this.methodsQueue.length
    
            if (!len) return  // Note:  This is required to prevent an infinite loop.
                              //        When we reset the childMethodsQueue array to empty,
                              //        it will trigger this method through the watch option,
                              //        so we need this in order to stop the cycle once we are done.
    
            // Note:  Instead of using ".shift()" to access an item in the array
            //        we need to use "[i]" otherwise we will get muliple calls of the method
            for (let i = 0; i < len; i++) {
              let method = this.methodsQueue[i]
              this[method.name](method.params)
            }
    
            // Note:  Now that we are done calling methods, we need to emit an event back to the parent
            //        so it can call it's method to reset the childMethodsQueue array to empty
            this.$emit('child-methods-finished')
          },
        },
      }
    

    【讨论】:

      【解决方案7】:

      为了将子组件与另一个子组件通信,我在父组件中创建了一个方法,该方法调用子组件中的方法:

      this.$refs.childMethod()
      

      我从另一个孩子那里调用了 root 方法:

      this.$root.theRootMethod()
      

      它对我有用。

      【讨论】:

      • 这个答案缺乏很多的解释
      猜你喜欢
      • 1970-01-01
      • 2015-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-09
      • 1970-01-01
      • 2021-11-07
      • 1970-01-01
      相关资源
      最近更新 更多