Vue子组件中调用方法的最佳实践

Posted

技术标签:

【中文标题】Vue子组件中调用方法的最佳实践【英文标题】:Vue best practice for calling a method in a child component 【发布时间】: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 等。

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

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

谢谢!

【问题讨论】:

【参考方案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 种其他方法可以进行通信:

事件总线 vuex

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

    在您的子组件上定义它

    props: ['testProp'],
    methods: 
      sayHello() 
        alert('hello');
      
    
    

    在父组件上定义一个触发数据

    data () 
     return 
       trigger: 0
     
    
    

    在父组件上使用prop

    <template>
     <div>
        <childComponent :testProp="trigger"/>
      </div>
    </template>
    

    在子组件中观察testProp并调用sayHello

    watch:  
        testProp: function(newVal, oldVal) 
          this.sayHello()
        
    
    

    从父组件更新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>

【讨论】:

以上是关于Vue子组件中调用方法的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

最佳实践:vue组件引用传值(续篇)

最佳实践:vue组件引用传值(续篇)

Vue:处理多个 API 调用的最佳实践

在 vue 组件中使用样式的最佳实践是啥?

来自 Vue 中不同组件的多个请求 - 最佳实践

在 vue 中呈现包含组件的 html 字符串的最佳实践