高级 Vue 技巧:控制父类的 slot入门百科_技巧小白指南

首先来思考一个问题:是否有一种方法可以从子组件填充父组件的插槽?最近一位同事问我这个问题,答案很简单:可以的。但我的解决方案可能和你想的完全不一样,这是涉及一个棘手的Vue架构问题,但也是一个非常有趣的问题。为什么会有这个问题在我们的应用程序中,我们有一个顶部栏,其中包含不同的按钮、搜索栏和其他一些控件。根据每个人所在的页面,它可能略有不同,因此我们需要一种

高级 Vue 技巧:控制父类的 slot入门百科

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

高级 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(传送门)

它们的工作方式和你想象的完全一样。你可以把任何东西从一个地方传送到另一个地方。在我们的例子中,我们将元素从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组件。

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

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

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

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

总结

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

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

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

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

海计划公众号
(0)
上一篇 2020/03/20 06:50
下一篇 2020/03/20 06:50

您可能感兴趣的内容

  • js 执行顺序入门攻略_顺序使用帮助

    1、js正常是顺序执行:A,B,C,不管A里边代码运行时间多长,都要先执行完A,再执行B,再执行CA:sssssssssssssssssssssssssss
    B: ddd
    C: ccccccccc 2、如果一个函数返回的是promise对象,则这个promise对象.then里边的代码,不会顺序执行,会同时执行,哪个代码运行快先显示哪个的执行结果jobs.a

    2020/04/03
  • HTTP协议知识点速览菜鸟知识_http小白攻略

    什么是HTTP?HTTP(HyperText Transfer Protocol),中文「超文本传输协议」。是互联网应用最广泛的网络协议基于 TCP 的应用层协议浏览器与服务器之间的数据传输协议。HTTP 协议和其他应用层协议一样,本质上是一种通信格式。HTTP 是信封,信封里面的信(HTML)是内容;但是没有信封,信也没办法寄出去。HTTP的特点无连接:每

    2020/03/22
  • 高并发下如何缩短响应时间?小白常识_高并发小白基础

    定义网站响应时间是指系统对请求作出响应的时间。通俗来讲就是我们把网址输入进浏览器然后敲回车键开始一直到浏览器把网站的内容呈现给用户的这段时间。网站响应时间是越短越好,因为网站页面打开速度越快,就意味着我们的用户可以更快的访问站点或者我们的服务器。一般我们网站的响应时间保持在100~1000ms即可。1m=1000ms,打开速度越快对用户体验度越好。据说响应时

    2020/03/30
  • 什么是浏览器对象BOM?菜鸟指南_bom使用教程

    什么是 BOM?BOM(Browser Object Model)即浏览器对象模型提供了独立于内容而与浏览器窗口进行交互的对象。由于 BOM 主要用于管理窗口与窗口之间的通讯,因此其核心对象是 window。 相关方法(属性)弹出框window.alert()URL 的属性// 当前 HTML 的 URL
    location.href
    // https:/

    2020/03/24
  • Deno基础知识教程_Node.js之父新造的轮子

    Deno基础知识教程 官方网址:https://deno.land/ GitHub:https://github.com/denoland/deno 简介描述:Node.js之父新…

    2020/03/06
  • 前端为什么学node?指南教程_前端入门指南

    随着互联网的高速发展以及市场需求推动,Node已经成为前端知识栈必备技能之一,很多企业在招聘中也会着重考察求职者对Node的掌握程度。那么就有人好奇了从事Web前端为什么要学习Node.js?下面本篇文章就来给大家详细的分析一下,希望对大家有所帮助。什么是Node.js?Node是一个让JavaScript运行在服务端的开发平台,它让JavaScript成为

    2020/03/22
  • 你可能不知道的CSS小白知识_规范入门百科

    前言也许有人会说,都快 2019 年了怎么还读 CSS2.1 规范。一方面,现在最新的 CSS (core) 规范是 CSS2.2(以下截图来自 https://www.w3.org/TR/CSS/ ),又因为 CSS2.1 有中文的版本,并且和 CSS2.2 规范差异性不是很大,基于偷懒的目的最终选择阅读了 CSS2.1 规范。记得面试的时候,面试官说 “

    2020/03/20
  • 面试中,如何证明自己是资深程序员?基础知识_面试新手入门

    真正的程序员为人处事方面相对比较低调,特别面试过程中不需要专门为了炫耀技术只是把面试官问的问题按部就班的回答上来就可以了,一般技术面试来讲技术面试官都会根据简历上情况做个大致的摸底,技术面试基本上通过面试就能了解个大概,因为根据简历上描述的项目经验直接问些相关的信息,提问几个具体实现方式很快就能检验出水平高低,如果回答得非常对口,可以再把问题细化,进一步挖掘

    2020/03/29
  • node里path是干什么的?使用帮助_node使用帮助

    Node.js path模块提供了一些用于处理文件路径的小工具,我们可以通过以下方式引入该模块:var path = require(“path”)path模块介绍:方法1、path.normalize(p)规范化路径,注意’..’ 和 ‘.’。2、path.join([path1][, path2][, …])用于连接路径。该方法的主要用途在于,会正确

    2020/03/22
  • Promise原理探究及实现使用帮助_Promise教程视频

    前言作为ES6处理异步操作的新规范,Promise一经出现就广受欢迎。面试中也是如此,当然此时对前端的要求就不仅仅局限会用这个阶段了。下面就一起看下Promise相关的内容。Promise用法及实现在开始之前,还是简单回顾下Promise是什么以及怎么用,直接上来谈实现有点空中花园的感觉。(下面示例参考自阮大佬es6 Promis,)定义Promise 是异

    2020/03/29
  • npm私有仓库 配置verdaccio在docker环境菜鸟教程下载_Docker使用帮助

    前端开发过程中,少不了自己封装一些通用的包,但又不想放在公共的平台,所以搭建一个npm私有的仓库是很有必要的。在这里简单介绍如何使用 verdoccio 在docker环境下的配置。verdoccio,轻量级私有npm代理注册表。加上docker,那就真的无敌方便了,搭建一个私有仓库轻轻松松几分钟搞定。首先要有nodejs 已经 npm 都已经安装好了。1.

    2020/04/03
  • 用 Jest 进行单元测试菜鸟教程网_测试基础知识教程

    测试的类型测试是用来检查你代码的代码。测试会使你对自己的程序更有信心。它们还能够防止你在修复一个 bug 时生成另一个 bug。你可以测试程序的方方面面,从单个函数及其返回值到在浏览器中运行的复杂程序。由于这是本课程的第一篇文章,因此我会简要对比一些流行的测试类型。单元测试单元测试覆盖了代码块,确保它们在运行时没有问题。被测试的单元可以是函数、模块和类等。单

    2020/03/23
  • css实现透明的两种方法基础入门_透明零基础入门

    一、opacity:0~1值越高,透明度越低:div{opacity:0.5
    }选择器匹配到的节点们,包括节点们的孩子节点,都会实现%50透明,另 0.5 可直接写成 .5二、rgba(0~255,0~255,0~255,0~1)rgba(0,0,0,0.5) :div{
    background-color:rgba(0,0,0,0.5);
    }选择器匹

    2020/03/24
  • React Rouer 使用教程菜鸟教程下载_router菜鸟教程网

    前言作为 React 全家桶的一员,如果我们想要开发一个 React 应用,那么 react-router 基本上是我们绕不过去的基础。基于此,对它的了解和使用也是必不可少的一步本文将重点介绍实际应用中常用的一些 API 以及实践过程中遇到的一些问题,目标很简单:会用基于 react-router v5.0.1,WEB 应用程序安装国际惯例,首先我们需要安装

    2020/03/26
  • ReplaceGoogleCDN小白攻略_一个 Chrome 插件:将 Google CDN 替换为国内的

    ReplaceGoogleCDN小白攻略 GitHub:https://github.com/justjavac/ReplaceGoogleCDN 简介描述:一个 Chrome 插…

    2020/03/11
  • js实现HSL与RGB色彩的相互转换功能基础知识教程_js技巧入门基础教程

    HSL与RGB描述RGB和HSL(也叫HSB/HSV)是两种色彩空间,即:红,绿,蓝(Red,Green,Blue)和色调,饱和度,亮度(Hue,Saturation,Lightness或Brightness或Value),前者适用于机器采样,目前的显示器颜色即由这三种基色构成,而后者更符合人类的直观感觉,比如人一般表达一个颜色会这样说:有点浓的暗红色。而不

    2020/04/05