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 相关文章推荐
JavaScript对象、属性、事件手册集合方便查询
Jul 04 Javascript
java与javascript之间json格式数据互转介绍
Oct 29 Javascript
JS、CSS加载中的小问题探讨
Nov 26 Javascript
jQuery中map()方法用法实例
Jan 06 Javascript
jQuery ajax提交Form表单实例(附demo源码)
Apr 06 Javascript
javascript实现根据汉字获取简拼
Sep 25 Javascript
详解jQuery中基本的动画方法
Dec 14 Javascript
vue router demo详解
Oct 13 Javascript
vue swipe自定义组件实现轮播效果
Jul 03 Javascript
JavaScript cookie原理及使用实例
May 08 Javascript
jQuery实现移动端笔触canvas电子签名
May 21 jQuery
vue-cli或vue项目利用HBuilder打包成移动端app操作
Jul 29 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
重置版宣传动画
2020/04/09 魔兽争霸
PHP数组排序函数合集 以及它们之间的联系分析
2013/06/27 PHP
php使用PDO获取结果集的方法
2017/02/16 PHP
PHP实现删除多重数组对象属性并重新赋值的方法
2017/06/07 PHP
tp5框架无刷新分页实现方法分析
2019/09/26 PHP
PhpStorm连接服务器并实现自动上传功能
2020/12/09 PHP
获取DOM对象的几种扩展及简写
2006/10/09 Javascript
js tab效果的实现代码
2009/12/26 Javascript
jquery 动态创建元素的方式介绍及应用
2013/04/21 Javascript
jquery cookie实现的简单换肤功能适合小网站
2013/08/25 Javascript
js实现幻灯片播放图片示例代码
2013/11/07 Javascript
利用JS生成博文目录及CSS定制博客
2016/02/10 Javascript
DOM中事件处理概览与原理的全面解析
2016/08/16 Javascript
JavaScript正则获取地址栏中参数的方法
2017/03/02 Javascript
JavaScript mixin实现多继承的方法详解
2017/03/30 Javascript
vue引入js数字小键盘的实现代码
2018/05/14 Javascript
Vue实现简易翻页效果源码分享
2018/11/08 Javascript
Echarts实现单条折线可拖拽效果
2019/12/19 Javascript
vue项目中监听手机物理返回键的实现
2020/01/18 Javascript
vue 二维码长按保存和复制内容操作
2020/09/22 Javascript
uni-app使用countdown插件实现倒计时
2020/11/01 Javascript
vue print.js打印支持Echarts图表操作
2020/11/13 Javascript
使用Python获取CPU、内存和硬盘等windowns系统信息的2个例子
2014/04/15 Python
python多线程操作实例
2014/11/21 Python
python解决pandas处理缺失值为空字符串的问题
2018/04/08 Python
对python:print打印时加u的含义详解
2018/12/15 Python
Django网络框架之HelloDjango项目创建教程
2019/06/06 Python
Python with标签使用方法解析
2020/01/17 Python
Python3 用matplotlib绘制sigmoid函数的案例
2020/12/11 Python
CSS3 创建网页动画实现弹跳球动效果
2018/10/30 HTML / CSS
英国第一摩托车和摩托车越野配件商店:GhostBikes
2019/03/10 全球购物
环保建议书400字
2014/05/14 职场文书
教师国庆节演讲稿范文2014
2014/09/21 职场文书
武侯祠导游词
2015/02/04 职场文书
2015年房地产个人工作总结
2015/05/26 职场文书
MySQL 分组查询的优化方法
2021/05/12 MySQL