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

use*_*612 24 vue.js

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

为了简化这个过程,我创建了一个我想要实现的非常简单的版本。

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

<template>
    <div>
        <button v-on:click="XXXXX call method in child XXXX">Say Hello</button>
    </div>
</template>
Run Code Online (Sandbox Code Playgroud)

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

methods: {
    sayHello() {

        alert('hello');
   }
  }
Run Code Online (Sandbox Code Playgroud)

我想sayHello()在单击父级中的按钮时调用该函数。

我正在寻找执行此操作的最佳实践方法。我看到的建议包括事件总线、子组件引用和道具等。

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

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

谢谢!

Fla*_*ame 56

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

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

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

  • 对于遇到与我相同问题的任何人,要添加到上面的答案,如果您使用“&lt;script setup&gt;”,请确保在子组件上添加“defineExpose({childFunction})”以公开该函数。另外,如果您正在调用具有多个实例的组件(即它位于 v-for 循环中),那么您需要通过索引调用它:“$refs.childComponent[x].childFunction()”。希望这对某人有帮助,我花了一段时间才弄清楚这一点,因为我找到的示例都不包含此信息! (31认同)
  • [警告](https://v3.vuejs.org/guide/component-template-refs.html) `$refs` 仅在组件渲染后才会填充。它只是作为直接子操作的逃生口 - 您应该避免从模板或计算属性中访问“$refs”。 (6认同)
  • 这应该是公认的答案。使用 prop/watcher 方法绝对没有任何好处。以这种方式使用 refs 不会像使用 prop/watcher 那样与子组件产生更多耦合。事实上,该方法要复杂得多,因为您必须深入了解子组件才能了解发生了什么。通过使用 ref,很明显正在调用子组件上的方法。 (5认同)
  • 最佳实践可能是将事件处理程序附加到单击事件,然后从那里访问“$refs.myChild” (2认同)

Jon*_*s m 41

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

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

子组件

<template>
  <div>
    <p>I was called {{ count }} times.</p>
  </div>
</template>
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

父组件

<template>
  <div>
    <button v-on:click="addCount">Add count to child</button>
    <child-component @interface="getChildInterface"></child-component>
  </div>
</template>
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

  • 当我在 1 分钟内实现这个并且它起作用时,我几乎晕倒了。这是世界新闻..! (5认同)
  • 这应该是选定的答案,因为它不会与 props、watchers 或 refs 混淆,保持 Vue 组件的真实性,并且只使用一些良好的老式编程原则。 (5认同)
  • 我简直不敢相信这是您 7 年来第一次对您的_GREAT_答案投赞成票...谢谢! (4认同)
  • @Vitim.us 是的,除了使用“ref”之外,没有取得任何成果,但不知何故,这台 Rube Goldberg 机器让人们投票、称赞原则、晕倒,并相信 1 个月就过去了 7 年。注意:在 Vue 组件上声明公共接口的官方且更简单的方法是 [`expose`](https://vuejs.org/api/options-state.html#expose)。 (4认同)
  • 我不明白它是如何解耦子进程和父进程的,如果你想重命名`addCounter`中的`addCount`,你还需要更改父进程和子进程吗? (2认同)
  • 这只是通过一系列复杂的回调和函数引用来公开内部方法,我根本不明白这是如何干净的。您仍然需要了解人造事件和参数之间的契约如何在子级和父级之间工作,就像子级中有一个方法,以便父级可以调用它一样,但步骤太多。唯一的好处是显式的“接口声明”,但这可以仅使用命名约定来存档。 (2认同)

MrS*_*Spt 29

通过 vue 3 组合 api,您可以使用defineExpose. 更多信息可以在这里找到

家长.vue

<script setup lang="ts">
const childRef = ref()

const callSayHello = () => {
  childRef.value.sayHello()
}
</script>

<template>
  <child ref="childRef"></child>
</template>

<style scoped></style>
Run Code Online (Sandbox Code Playgroud)

儿童.vue

<script setup lang="ts">
const sayHello = () => {
  console.log('Hello')
}
defineExpose({ sayHello })
</script>

<template></template>

<style scoped></style>
Run Code Online (Sandbox Code Playgroud)


Rad*_*iță 20

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

您应该依靠contract(某些框架/语言中的接口)来实现这一点。将contractVue依赖于家长通过与孩子沟通的事实props,并children与之通信parent通过events

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

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

1.在你的子组件上定义

props: ['testProp'],
methods: {
 sayHello() {
    alert('hello');
 }
}
Run Code Online (Sandbox Code Playgroud)

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

data () {
 return {
   trigger: 0
 }
}
Run Code Online (Sandbox Code Playgroud)

3.在父组件上使用prop

<template>
 <div>
    <childComponent :testProp="trigger"/>
  </div>
</template>
Run Code Online (Sandbox Code Playgroud)

4.testProp在子组件中watch并调用sayHello

watch: { 
    testProp: function(newVal, oldVal) {
      this.sayHello()
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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