在 Vue 中有两种方式来实现这种效果,一种是使用指令,操作真实 dom,使用熟知的 dom 操作方法将指令所在的元素 append
到另外一个 dom 节点上去。另一种方式就是定义一套组件,将组件内的 vnode 转移到另外一个组件中去,然后各自渲染。
它们的工作方式和你想象的完全一样。你可以把任何东西从一个地方传送到另一个地方。在我们的例子中,我们将元素从DOM中的一个位置“传送”到另一个位置。
无论组件树如何显示,我们都可以控制组件在DOM中的显示位置。
例如,假设我们想要填充一个modal。但是我们的modal必须在根页面处渲染,这样我们才能正确地覆盖它。首先,我们要在modal中指定我们想要的:
<template> <div> <!-- Other components --> <Portal to="modal"> Rendered in the modal. </Portal> </div> </template>
然后,在我们的modal组件中,我们将拥有另一个将内容渲染出来的 portal:
<template> <div> <h1>Modal</h1> <Portal from="modal" /> </div> </template>
这是一项改进,因为现在我们实际上是在编写HTML,而不仅仅是传递对象。 它更具声明性,更容易查看应用程序中发生的事情。
由于 portal 在背后执行一些操作以在不同位置渲染元素,因此它完全打破了DOM渲染在Vue中工作方式的模型。 看起来您正在正常渲染元素,但根本无法正常工作,这可能会引起很多混乱和沮丧。
还有一个很大的问题,稍后我们会讲到。
提升状态
“提升状态”是指将状态从子组件移动到父组件或祖父组件,将它向上移动到组件树中。
这可能对应用程序的体系结构产生较大的影响。对于我们的目的,这会是更简单的解决方案。
这里的“状态”是我们试图传递到ActionBar组件插槽中的内容。但是该状态包含在Page组件中,我们不能真正将 page 特定的逻辑移到layout组件中。 我们的状态必须保留在我们正在动态渲染的Page组件内。
因此,我们必须提升整个Page组件才能提升状态。当前,我们的Page组件是Layout组件的子组件:
<template> <div> <FullPageError /> <ActionBar /> <Page /> </div> </template>
解除它需要我们将其翻转,并使Layout组件成为Page组件的子组件。 我们的Page组件看起来像这样:
<template> <Layout> <!-- Page-specific content --> </Layout> </template>
现在,我们的Layout组件将看起来像这样,我们可以在其中使用插槽插入页面内容:
<template> <div> <FullPageError /> <ActionBar /> <slot /> </div> </template>
但这还不能让我们自定义任何内容。 我们必须在Layout组件中添加一些命名的插槽,以便我们可以传递应放置在ActionBar中的内容。
最简单的方法是使用一个插槽来完全替代ActionBar组件:
<template> <div> <FullPageError /> <slot> <ActionBar /> </slot> <slot /> </div> </template>
这样,如果你不指定“actionbar”插槽,默认使用ActionBar组件。 但我们可以使用自己的自定义ActionBar配置覆盖此插槽:
<template> <Layout> <template #actionbar> <ActionBar> <!-- Custom content that goes into the action bar --> </ActionBar> </template> <!-- Page-specific content --> </Layout> </template>
对我来说,这是一种理想的处理方式,但是它确实需要我们重构页面的布局方式。 对于界面复杂点的,这可能是一项艰巨的任务。
简化一下
当我们第一次定义问题时:
我们可以让子组件填充父组件的插槽吗?但实际上,这个问题与props没有任何关系。 更简单地说,它是关于使子组件控制在其自己的子树之外渲染的内容。
我们可以这样表述问题
组件控制在其子组件之外渲染的内容的最佳方法是什么?通过这个镜头检查我们提出的每个解决方案,都会为我们提供一个有趣的新视角。
向父组件发出事件
数据流经组件树的唯一途径是使用 props。 而向上通信的方法是使用事件。这意味着,如果要让子组件与父组件进行通信,我们需要使用事件来实现。
静态配置
只是将必要的信息提供给其他组件,而不是主动地要求另一个组件做事情。
传送门
组件无法控制其子树之外的内容。这里的每个方法都是让另一个组件执行我们的命令并控制我们真正感兴趣的元素不同的方式。
在这方面,使用 portal 更好的原因是它们允许我们将所有这些通信逻辑封装到单独的组件中。
提升状态
提升状态是一种比我们前面看到的3种更简单、更强大的技术,这里我们的主要限制是我们想要控制的内容在子组件之外。
最简单的解决方法是: