Vue 技巧之控制父类的 slot

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

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

为什么会有这个问题

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

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

这个顶部栏(我们称之为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的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解vue slot插槽的使用方法

    官方文档其实已经讲得很详细,我根据文档,把官方的小案例实现了一下,这样更直观 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="https://unpkg.com/vue@2.3.3/dist/vue.js"><

  • 深入理解vue中slot与slot-scope的具体使用

    写在前面 vue中关于插槽的文档说明很短,语言又写的很凝练,再加上其和methods,data,computed等常用选项使用频率.使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生"算了吧,回头再学,反正已经可以写基础组件了",于是就关闭了vue说明文档. 实际上,插槽的概念很简单,下面通过分三部分来讲.这个部分也是按照vue说明文档的顺序来写的. 进入三部分之前,先让还没接触过插槽的同学对什么是插槽有一个简单的概念:插槽,也就是slot,是组件的一块HTML模板,这块模板

  • vue中slot(插槽)的介绍与使用

    什么是插槽? 插槽(Slot)是Vue提出来的一个概念,正如名字一样,插槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性.插槽显不显示.怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制 Vue slot 原理 在web-components中有slot的概念,https://developers.google.com/web/fundamentals/web-components/shadowdom. <slot> 元素 Shado

  • Vuejs第十一篇组件之slot内容分发实例详解

    什么是组件? 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展. Slot分发内容 ①概述: 简单来说,假如父组件需要在子组件内放一些DOM,那么这些DOM是显示.不显示.在哪个地方显示.如何显示,就是slot分发负责的活. ②默认情况下 父组件在子组件内套的内容,是不显示的. 例如代码: <

  • Vue.js之slot深度复制详解

    前言 在Vue中,slot是一个很有用的特性,可以用来向组件内部插入一些内容.slot就是"插槽"的意思,用大白话说就是:定义组件的时候留几个口子,由用户来决定插入的内容. 例如我们定义一个组件MyComponent,其包含一个slot: Vue.component('MyComponent', { template: ` <div> <slot></slot> </div> ` }) 当调用<MyComponent>123&

  • vue 组件中slot插口的具体用法

    子组件 <template> <div class="slotcontent"> <ul> <!--<slot></slot>--> <li v-for="item in items">{{item.text}}</li> </ul> </div> </template> <script> export default{ d

  • vue中父子组件注意事项,传值及slot应用技巧

    一.父子组件传值 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>父子组件传值</title> <style> </style> <script src="./vue.js"></script> </head> <bod

  • 深入浅析Vue中的slots/scoped slots

    一直对Vue中的slot插槽比较感兴趣,下面是自己的一些简单理解,希望可以帮助大家更好的理解slot插槽 下面结合一个例子,简单说明slots的工作原理 dx-li子组件的template如下: <li class="dx-li"> <slot> 你好! </slot> </li> dx-ul父组件的template如下: <ul> <dx-li> hello juejin! </dx-li> <

  • Vue 技巧之控制父类的 slot

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

  • 15个Vue技巧,你都知道吗

    目录 前言 1.将一个prop限制在一个类型的列表中 2.使用引号来监听嵌套属性 3.知道何时使用v-if 4.单个作用域插槽的简写 5.将局部和全局的style混合在一起 6.重写子组件的样式 7.如何在Vue之外创建一个具有响应性的变量 8.v-for中的解构 9.在指定范围内循环 10.窃取prop类型 11.检测元素外部(或内部)的单击 12.从组件外部调用一个方法 13.监听数组和对象 14.template标签的另一个用途 15.处理错误(和警告)的更好方法 前言 学习成为一个更好的

  • Vue技巧Element Table二次封装实战示例

    目录 前言 思考 实践 filterPane.vue 明确目标 传入数据结构整理 timeSelect elinput elselect 开始封装 tablePane.vue 明确目标 传入数据结构整理 tool cols pageData operation tablePane.vue配置项Cols详解 开始封装 实战 结尾 前言 由于重构后台管理项目中有好多表格页面, 举个栗子 这表格看着还挺好看,写起来叫人直呼XX,多动脑子少掉发,少走弯路多省鞋. 写了一个后感觉太麻烦了,于是我奋笔疾书,

  • 你不知道的Vue技巧之--开发一个可以通过方法调用的组件(推荐)

    Vue作为最近最炙手可热的前端框架,其简单的入门方式和功能强大的API是其优点.而同时因为其API的多样性和丰富性,所以他的很多开发方式就和一切基于组件的React不同,如果没有对Vue的API(有一些甚至文档都没提到)有一个全面的了解,那么在开发和设计一个组件的时候有可能就会绕一个大圈子,所以我非常推荐各位在学习Vue的时候先要对Vue核心的所有API都有一个了解. 举个例子,通知组件notification基本是现代web开发标配,在很多地方都能用到.而在以Vue作为核心框架的前端项目中,因

  • 一定要知道的 25 个 Vue 技巧

    目录 1. 将 prop 限制为类型列表 2. 默认内容和扩展点 3. 使用引号观察嵌套值 4. 知道何时使用 v-if(以及何时避免使用) 5. 单作用域 slot 的简写(不需要模板标签!) 6. 有条件地渲染slot 6.1 为什么我们希望能够有条件地渲染slot呢? 7. 如何观察slot的变化 8. 将本地和全局风格混合在一起 9. 覆盖子组件的样式--正确的方法 10. 用上下文感知组件创造魔法 10.1 状态共享 10.2 配置 10.3 造型 11. 如何使在 Vue 之外创建的

  • Vue.js中组件中的slot实例详解

    Vue组件中的slot slot 可以实现在已经定义的组件中添加内容,组件会接收内容并输出,假如有一个组件person,它的里面包含的是个人信息,如下面这样 <template id="per"> <div> <p>姓名:...</p> <p>年龄:...</p> <p>职业:...</p> </div> </template> 在应用的时候,当然希望这里面可以是灵活

  • Vue 页面权限控制和登陆验证功能的实例代码

    页面权限控制 页面权限控制是什么意思呢? 就是一个网站有不同的角色,比如管理员和普通用户,要求不同的角色能访问的页面是不一样的.如果一个页面,有角色越权访问,这时就得做出限制了. Vue 动态添加路由及生成菜单 这是我写过的一篇文章, 通过动态添加路由和菜单来做控制,不能访问的页面不添加到路由表里,这是其中一种办法. 另一种办法就是所有的页面都在路由表里,只是在访问的时候要判断一下角色权限.如果有权限就让访问,没有权限就拒绝,跳转到 404 页面. 思路: 在每一个路由的 meta 属性里,将能

  • vue组件详解之使用slot分发内容

    一.什么是slot 在使用组件时,我们常常要像这样组合它们: <app> <app-header></app-header> <app-footer></app-footer> </app> 当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot , 这个过程叫作内容分发( transclusion ). 注意两点: 1.< app>组件不知道它的挂载点会有什么内容.挂载点的内容是由<app >

  • 解决vue点击控制单个样式的问题

    既然是控制单个样式,我们的html里面的内容一定是v-for="":渲染出来,一定要养成一个好习惯,v-for="(item,index) in items";index就是我们所说的索引. <div class="border" v-for="(item,index) in tolos" :key="index"> 我做的项目类似于微信朋友圈,弹出赞与评论按钮,点击一个全体都会弹出:我们要解决

  • vue实现权限控制路由(vue-router 动态添加路由)

    用户登录后返回权限菜单,前端根据权限菜单动态添加路由,然后再动态生成菜单栏. 思路如下: 一.定义初始化默认路由. 二.动态配置路由,这里是把所有组件中相应的路由配置成一个个的对象,根据后台返回的菜单tree一个个去匹配. 三.通过匹配,把匹配好的路由数据addRoutes到路由中. 四.为了防止刷新页面后路由数据被清空,这里用判断是否登录的方式,再次加载动态路由. 具体代码如下: router.js import Vue from 'vue' import {router} from './i

随机推荐