【问题标题】:Vue best practice for calling a method in a child componentVue子组件中调用方法的最佳实践
【发布时间】:2019-08-14 10:28:27
【问题描述】:

我已经阅读了很多关于此的文章,似乎有多种方法可以做到这一点,许多作者建议不要使用某些实现。

为了简单起见,我创建了一个我想要实现的非常简单的版本。

我有一个父级 Vue,parent.vue。它有一个按钮:

<template>
    <div>
        <button v-on:click="XXXXX call method in child XXXX">Say Hello</button>
    </div>
</template>

在子Vue中,child.vue我有一个带函数的方法:

methods: {
    sayHello() {

        alert('hello');
   }
  }

我想在点击父级按钮时调用sayHello()函数。

我正在寻找执行此操作的最佳实践方法。我看到的建议包括 Event Bus 和 Child Component Refs 和 props 等。

在我的方法中执行函数的最简单方法是什么?

抱歉,这看起来确实非常简单,但我确实尝试过做一些研究。

谢谢!

【问题讨论】:

    标签: vue.js


    【解决方案1】:

    我不确定这是不是最好的方法。但我可以解释我能做什么...... Codesandbox 演示:https://codesandbox.io/s/q4xn40935w

    从父组件发送一个prop 数据让我们说msg。每当单击按钮切换msg true/false时,父级都有一个button

    <template>
      <div class="parent">
        Button from Parent : 
        <button @click="msg = !msg">Say Hello</button><br/>
        <child :msg="msg"/>
      </div>
    </template>
    
    <script>
    import child from "@/components/child";
    
    export default {
      name: "parent",
      components: { child },
      data: () => ({
        msg: false
      })
    };
    </script>
    

    在子组件watch 道具数据msg。每当msg 更改触发一个方法。

     <template>
      <div class="child">I am Child Component</div>
    </template>
    
    <script>
    export default {
      name: "child",
      props: ["msg"],
      watch: {
        msg() {
          this.sayHello();
        }
      },
      methods: {
        sayHello() {
          alert("hello");
        }
      }
    };
    </script>
    

    【讨论】:

      【解决方案2】:

      一个简单的方法是这样做:

      <!-- parent.vue -->
      <template>
          <button @click="$refs.myChild.sayHello()">Click me</button>
          <child-component ref="myChild" />
      </template>
      

      只需为子组件创建一个ref,您就可以调用方法,并访问它拥有的所有数据。

      【讨论】:

      • 这应该是公认的答案。使用 prop/watcher 方法绝对没有任何好处。以这种方式使用 refs 不会像使用 prop/watcher 那样创建与子组件的更多耦合。实际上,该方法要复杂得多,因为您必须深入研究子组件才能了解发生了什么。通过使用 ref,很明显正在调用子组件上的方法。
      • WARNING $refs 仅在组件渲染后填充。它仅作为直接子操作的逃生舱口 - 您应该避免从模板或计算属性中访问 $refs
      • @vch 我同意这一点,但替代方案是此页面上其他复杂的答案之一。但是,在您尝试控制它之前,无论如何都必须渲染您想要控制的组件,这在任何情况下都是如此。这里的其他答案之一使用属性作为触发器;有趣的是,即使根本没有子组件对其做出反应,您也可以随心所欲地更改该属性。在我的回答中,您确定当 $refs.myChild 未定义时会出现错误。
      • 最佳实践可能是将事件处理程序附加到点击事件,然后从那里访问$refs.myChild
      • 您需要对子组件中的 sayHello() 方法做任何特定的事情吗?我已经根据这个答案和其他类似的答案进行了尝试,但无法解决一个错误,即该函数不存在,即使它在子组件中添加并可用。
      【解决方案3】:

      您可以创建ref 并访问方法,但不建议这样做。您不应该依赖组件的内部结构。这样做的原因是您将紧密耦合组件,而创建组件的主要原因之一是松散耦合它们。

      您应该依靠合约(某些框架/语言中的接口)来实现这一点。 Vue 中的契约依赖于父母通过props 与孩子交流,孩子通过events 与父母交流的事实。

      当您想要在非父/子组件之间进行通信时,还有至少 2 种其他方法可以进行通信:

      我现在将描述如何使用道具:

      1. 在您的子组件上定义它

        props: ['testProp'],
        methods: {
          sayHello() {
            alert('hello');
          }
        }
        
      2. 在父组件上定义一个触发数据

        data () {
         return {
           trigger: 0
         }
        }
        
      3. 在父组件上使用prop

        <template>
         <div>
            <childComponent :testProp="trigger"/>
          </div>
        </template>
        
      4. 在子组件中观察testProp并调用sayHello

        watch: { 
            testProp: function(newVal, oldVal) {
              this.sayHello()
            }
        }
        
      5. 从父组件更新trigger。确保始终更改trigger 的值,否则watch 不会触发。一种方法是增加触发器,或将其从真值切换为假值 (this.trigger = !this.trigger)

      【讨论】:

      • 我不喜欢这里使用道具作为触发器的方式。我怀疑这是 vue 推荐的解决方案。当您更改 trigger 变量时,不清楚您是否正在调用函数。您现在与调用$refs.childComp.sayHello() 一样依赖“组件的内部结构”,因为testPropsayHello() 一样是一个属性。
      • 实际上使用 props 进行父子之间的交流是推荐的方式。您不依赖于组件的内部结构,而是依赖于组件公开的 props,这与接口或类的公共字段的行为方式非常相似。目前,prop 调用 sayHello 的事实是一个实现细节。父母不应该依赖这一点,它应该依赖于 testProp 所做的事情被记录在案的事实,而不是查看实现。
      • 如果您正在演示如何使用 prop 和 watcher 调用函数,那么这是一个糟糕的设计并且仍然耦合。更好的方法是发出一个“triggerAlert”事件并在父级中实现该方法,但就个人而言,有时这会违反 SRP。就个人而言,我认为在有意义的地方调用公开暴露的方法没有任何问题。我看到太多人扭曲实现以适应 props-down/event-up 的实例,而他们只能进行方法调用。
      • “使用 refs 和调用方法是不好的,让我们找到一个仍然依赖于内部结构的复杂方法”。真的是语义。
      • 这绝对比使用 ref 并直接调用方法更糟糕。你和以前一样依赖子组件,但现在你的代码逻辑也变得不可读了。
      【解决方案4】:

      一个简单的解决方案是使用 vanilla JS 中的custom events

      在父组件中:

      const customEvent = new CustomEvent('custom-event');
      document.dispatchEvent(customEvent);
      

      在子组件中:

      document.addEventListener('custom-event', this.functionToCall);
      

      【讨论】:

      • 虽然它解决了问题,但从长远来看,这在代码可读性方面可能是一个糟糕的解决方案。当问题要求“最佳实践”时尤其如此
      【解决方案5】:

      我不喜欢使用 props 作为触发器的样子,但使用 ref 似乎也是一种反模式,通常不推荐。

      另一种方法可能是:您可以使用事件来公开方法接口来调用子组件,这样您就可以在保持代码干净的同时获得两全其美的效果。只需在安装阶段发射它们并在高兴时使用它们。我将它存储在下面代码中的 $options 部分,但您可以随心所欲。

      子组件

      <template>
        <div>
          <p>I was called {{ count }} times.</p>
        </div>
      </template>
      
      <script>
        export default {
          mounted() {
            // Emits on mount
            this.emitInterface();
          },
          data() {
            return {
              count: 0
            }
          },
          methods: {
      
            addCount() {
              this.count++;
            },
      
            notCallable() {
              this.count--;
            },
      
            /**
             * Emitting an interface with callable methods from outside
             */
            emitInterface() {
              this.$emit("interface", {
                addCount: () => this.addCount()
              });
            }
      
          }
        }
      </script>
      

      父组件

      <template>
        <div>
          <button v-on:click="addCount">Add count to child</button>
          <child-component @interface="getChildInterface"></child-component>
        </div>
      </template>
      
      <script>
        export default {
          // Add a default
          childInterface: {
            addCount: () => {}
          },
      
          methods: {
            // Setting the interface when emitted from child
            getChildInterface(childInterface) {
              this.$options.childInterface = childInterface;
            },
      
            // Add count through the interface
            addCount() {
              this.$options.childInterface.addCount();
            }
          }
        }
      </script>
      

      【讨论】:

        猜你喜欢
        • 2019-04-29
        • 1970-01-01
        • 1970-01-01
        • 2021-07-03
        • 2022-09-28
        • 2021-06-10
        • 2017-01-20
        • 1970-01-01
        • 2019-04-25
        相关资源
        最近更新 更多