Vue 技巧之控制父类的 slot


Posted in Javascript onFebruary 24, 2020

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

最近一位同事问我这个问题,答案很简单:可以的。但我的解决方案可能和你想的完全不一样,这是涉及一个棘手的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
这种组件就希望能够脱离当前模板所在的层叠上下文。

在 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 name="actionbar">
   <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种更简单、更强大的技术,这里我们的主要限制是我们想要控制的内容在子组件之外。

最简单的解决方法是:

提升状态以及操纵该状态的逻辑,使我们可以拥有更大范围的组件,并将目标元素包含在该组件中。如果可以这样做,这是解决此特定问题以及所有相关问题的最简单方法。

请记住,这并不一定意味着要提升整个组件。 你也可以重构你的应用程序,以将逻辑移到组件树中更高的组件中。

依赖注入

如果熟悉软件工程设计模式的人可能已经注意到,我们在这里所做的是依赖注入,这是我们在软件工程中已经使用了几十年的技术。

它的用途之一是编写易于配置的代码。在我们的例子中,,我们在使用的每个Page中以不同的方式配置Layout组件。

当调换PageLayout组件时,我们正在执行所谓的控件反转。

在基于组件的框架中,父组件控制子组件的操作,因此我们选择让Page来控制Layout组件,而不是由Layout组件控制Page。

为了做到这一点,我们使用插槽为Layout组件提供完成任务所需的内容。

正如我们所看到的,使用依赖注入可以使我们的代码更加模块化和易于配置。

总结

我们讨论了解决这个问题的4种不同方法,展示了每种方法的优缺点。然后我们更进一步,将问题转化为一个更一般的问题,即控制组件子树之外的内容。

、提升状态和依赖项注入是两个非常有用的模式。它们是我们武器库中最好的工具,因为它们可以应用于无数的软件开发问题。

但最重要的是,希望你还能学会:

通过使用一些常见的软件模式,将一个丑陋解决方案的问题转变成一个非常优雅的问题。许多其他的问题都可以用这种方法解决,即把一个丑陋的、复杂的问题转化成一个更简单、更容易解决的问题。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

原文:https://dev.to/michaelthiesse...

以上就是Vue 技巧之控制父类的 slot的详细内容,更多关于Vue控制父类的 slot的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
JQuery实现自定义对话框的代码
Jun 15 Javascript
jQuery 顺便学习下CSS选择器 奇偶匹配nth-child(even)
May 24 Javascript
js判断为空Null与字符串为空简写方法
Feb 24 Javascript
js重写alert控件(适合学习js的新手朋友)
Aug 24 Javascript
js控制鼠标事件移动及移出效果显示
Oct 19 Javascript
零基础搭建Node.js、Express、Ejs、Mongodb服务器及应用开发入门
Dec 20 Javascript
Javascript中匿名函数的调用与写法实例详解(多种)
Jan 26 Javascript
Jquery Easyui自定义下拉框组件使用详解(21)
Dec 31 Javascript
bootstrap实现的自适应页面简单应用示例
Mar 09 Javascript
javaScript实现复选框全选反选事件详解
Nov 20 Javascript
详解vue中axios的封装
Jul 18 Javascript
Vue infinite update loop的问题解决
Apr 23 Javascript
原生javascript的ajax请求及后台PHP响应操作示例
Feb 24 #Javascript
在 Vue 中编写 SVG 图标组件的方法
Feb 24 #Javascript
原生javascript中this几种常见用法总结
Feb 24 #Javascript
js实现坦克大战游戏
Feb 24 #Javascript
Vue中点击active并第一个默认选中功能的实现
Feb 24 #Javascript
如何在JavaScript中创建具有多个空格的字符串?
Feb 23 #Javascript
浅谈TypeScript的类型保护机制
Feb 23 #Javascript
You might like
php中几种常见安全设置详解
2010/04/06 PHP
使用VisualStudio开发php的图文设置方法
2010/08/21 PHP
SWFUpload与CI不能正确上传识别文件MIME类型解决方法分享
2011/04/18 PHP
php中通过curl模拟登陆discuz论坛的实现代码
2012/02/16 PHP
php快递单号查询接口使用示例
2014/05/05 PHP
php使用FFmpeg接口获取视频的播放时长、码率、缩略图以及创建时间
2016/11/07 PHP
ThinkPHP框架结合Ajax实现用户名校验功能示例
2019/07/03 PHP
自己动手制作jquery插件之自动添加删除行功能介绍
2011/10/14 Javascript
js的压缩及jquery压缩探讨(提高页面加载性能/保护劳动成果)
2013/01/29 Javascript
setinterval()与clearInterval()JS函数的调用方法
2015/01/21 Javascript
理解jquery事件冒泡
2016/01/03 Javascript
jQuery实现table中的tr上下移动并保持序号不变的实例代码
2016/07/11 Javascript
jQuery得到多个值只能用取Class ,不能用取ID的方法
2016/12/04 Javascript
JS 对java返回的json格式的数据处理方法
2016/12/05 Javascript
基于JavaScript实现瀑布流效果
2017/03/29 Javascript
微信小程序实现图片压缩功能
2018/01/26 Javascript
webpack 模块热替换原理
2018/04/09 Javascript
详解微信小程序开发(项目从零开始)
2019/06/06 Javascript
JS canvas实现画板和签字板功能
2021/02/23 Javascript
[00:26]TI7不朽珍藏III——冥界亚龙不朽展示
2017/07/15 DOTA
对python中if语句的真假判断实例详解
2019/02/18 Python
Python循环中else,break和continue的用法实例详解
2019/07/11 Python
解决python3 requests headers参数不能有中文的问题
2019/08/21 Python
Pandas实现dataframe和np.array的相互转换
2019/11/30 Python
Python Lambda函数使用总结详解
2019/12/11 Python
在pycharm中实现删除bookmark
2020/02/14 Python
Python操作Jira库常用方法解析
2020/04/10 Python
canvas绘制圆角头像的实现方法
2019/01/17 HTML / CSS
英国汽车座椅和婴儿车购物网站:Uber Kids
2017/04/19 全球购物
2014年班组长工作总结
2014/11/20 职场文书
再读《皇帝的新衣》的读后感悟!
2019/08/07 职场文书
中秋节作文(五年级)之关于月亮
2019/09/11 职场文书
基于JavaScript实现年月日三级联动
2021/06/22 Javascript
Mysql数据库表中为什么有索引却没有提高查询速度
2022/02/24 MySQL
docker-compose部署Yapi的方法
2022/04/08 Servers
Elasticsearch 批量操作
2022/04/19 Python