Vue 技巧之控制父类的 slot

首先来思考一个问题:是否有一种方法可以从子组件填充父组件的插槽?

最近一位同事问我这个问题,答案很简单:可以的。但我的解决方案可能和你想的完全不一样,这是涉及一个棘手的Vue架构问题,但也是一个非常有趣的问题。

为什么会有这个问题

Vue 技巧之控制父类的 slot

在我们的应用程序中,我们有一个顶部栏,其中包含不同的按钮、搜索栏和其他一些控件。根据每个人所在的页面,它可能略有不同,因此我们需要一种基于每个页面配置它的方法。

Vue 技巧之控制父类的 slot

为此,我们希望每个页面都能够配置操作栏。看起来很简单,但这里有个问题

这个顶部栏(我们称之为ActionBar)实际上是我们的主布局的一部分,结构如下:

<template> <div> <FullPageError /> <ActionBar /> <App /> </div> </template>

根据你所在的页面/路线动态注入App的位置。

我们可以使用ActionBar上的一些插槽来配置它。 但是,我们如何从App组件中控制这些插槽?

定义问题

首先,最好是尽可能清楚地知道我们要解决的问题。

我们来看一个具有一个子组件和一个插槽的组件:

// Parent.vue <template> <div> <Child /> <slot /> </div> </template>

我们可以这样填充Parent的插槽:

// App.vue <template> <Parent> <p>This content goes into the slot</p> </Parent> </template>

这里没什么特别的。。。

填充子组件的插槽很容易,这也是使用插槽的最常见方式。

但是,有没有一种方法可以控制从Child组件内部进入Parent组件slot的内容呢?

换种说法:我们可以让子组件填充父组件的插槽吗?来看看我想到的第一个解决方案。

向下使用 props,向上使用 event

数据流经组件树的唯一途径是使用props。 而向上通信的方法是使用事件。这意味着,如果要让子组件与父组件进行通信,我们需要使用事件来实现。

因此,我们将使用事件来将内容传递到ActionBars槽中

import SlotContent from './SlotContent'; export default { name: 'Application', created() { // As soon as this component is created we'll emit our events this.$emit('slot-content', SlotContent); } };

我们将要放入插槽中的所有内容打包到SlotContent组件中。 一旦创建了应用程序组件,我们就会发出slot-content事件,并传递我们要使用的组件。

我们的组件结构如下:

<template> <div> <FullPageError /> <ActionBar> <Component :is="slotContent" /> </ActionBar> <App @slot-content="component => slotContent = component" /> </div> </template>

监听该事件,并将slotContent设置为我们的App组件发送给我们的任何内容。 然后,使用内置的Component,就可以动态地渲染该组件。

但是,通过事件传递组件感觉很奇怪,并非是主流的做法。幸运的是,还有一种方法可以完全避免使用事件。

使用 $options

由于Vue组件只是 JS 对象,因此我们可以向它们添加所需的任何属性。无需使用事件传递插槽内容,我们只需将其作为字段添加到组件中即可:

// App.vue import SlotContent from './SlotContent'; export default { name: 'Application', slotContent: SlotContent, props: { /***/ }, computed: { /***/ }, };

在主页中通过 App.slotContent 获取对应的组件

<template> <div> <FullPageError /> <ActionBar> <Component :is="slotContent" /> </ActionBar> <App /> </div> </template> import App from './App'; import FullPageError from './FullPageError'; import ActionBar from './ActionBar'; export default { name: 'Scaffold', components: { App, FullPageError, ActionBar, } data() { return { slotContent: App.slotContent, } }, };

这更像是静态配置,更美观、更简洁,但这仍然是不对的。

理想情况下,我们不会在代码中混合使用范式,所有操作应该都是以声明方式完成。

但是在这里,我们没有将我们的组件组合在一起,而是将它们作为 JS 对象传递。如果我们能以正常的Vue方式把我们想要的写在插槽里就好了。

考虑 Portal(传送门)

Vue 中的 Portal 技术 在 Vue 项目中,我们使用模板来声明 dom

嵌套关系,然而有时候一些组件需要脱离固定的层级关系,不再受制与层叠上下文,比如说 Modal 和 Dialog
这种组件就希望能够脱离当前模板所在的层叠上下文。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/3d4cdc576253379f95c1640d0c77a4b2.html