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()
一样依赖“组件的内部结构”,因为testProp
和sayHello()
一样是一个属性。
实际上使用 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子组件中调用方法的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章