vue和react中关于插槽详解

目录
  • 简述Slot
  • 基本插槽
    • vue基本插槽
    • react基本插槽
  • 具名插槽
    • vue具名插槽
    • react具名插槽的讨论
    • 模仿具名插槽
  • 属性插槽
  • 插槽传参
    • vue插槽传参
    • react:render-props

简述Slot

slot插槽是Vue对组件嵌套这种扩展机制的称谓,在react可以也这样称呼,但是并不很常见。不过叫slot确实很形象。

这样的形式就是slot插槽:

vue

<template>
  <container-comp>
    <content></content>
    <footer></footer>
  </container-comp>
</template>

react

() => (
  <ContainerComp>
    <Content />
    <Footer />
  </ContainerComp>
)

(我们可以把container-comp称之为容器组件,把content、footer称之为子组件)

这种机制的好处主要在于,在某个容器提供的模版或者数据中,可以根据需求灵活扩展需要渲染的子组件。专业点说就是通过容器和子组件之间的协议(数据交换和渲染方式),将彼此逻辑独立解藕,提升了各自的复用性。

举个例子,容器组件提供了一份渲染模版,将各个模块的位置预留出来,使用的时候根据各个子组件的顺序或者插槽名称,在不同场景可以选择不同的子组件。

再举个使用插槽的例子,容器组件中提供一些数据,比如定时请求某种接口得到数据,或者监听,订阅一些数据,比如在挂载完成后监听鼠标事件,得到位置数据。数据拿到之后,具体的对数据的渲染方式交给子组件来做,而这时候,容器可以通过参数传递的方式将得到的数据交给子组件。

通过上面简单的的例子描述,可以看到,使用插槽的的代码设计符合单一职责原则,逻辑更加内聚。

而不管是vue还是react上述描述的这些功能都是支持的,只是有的叫法略有所别,但是其目的一致。(因为近期在项目中使用vue多一点,而之前对vue的了解只是笼统的学习过响应式原理,并没有真正在项目里写过vue,所以,近期希望结合vue和react,来系统的回顾回顾相关的知识点。)

下面就先看下vue中的插槽都有什么功能。一边看vue,一边对比react。 参考这里:vue2官网:slot,可以看到,插槽相关的核心功能有:

  • 基本渲染插槽内容;
  • 具名插槽:一个容器多个插槽,需要分别命名;
  • 插槽传递参数,属于相对高级点的用法;

基本插槽

vue基本插槽

最简单插槽用法就像下面这样:

<template>
  <main-comp>
    <div>内容</div>
    <!--
    可以是任何自定义组件
    <my-sub></my-sub>
    -->
  </main-comp>
</template>

main-comp就是容器组件,而我们将<div>内容</div>作为容器的子元素,那么,容器里面怎么写呢?

<template>
  <div class="main">
    <slot>后备信息,以防万一</slot>
  </div>
</template>

可以看到,在容器里面简单的使用slot标签,相当于占位。当组件渲染的时候,<slot></slot> 将会被替换为<div>内容</div>,如果没有使用时插槽的话,会渲染出slot标签内的内容:“后备信息,以防万一”。当然,使用的插槽不止是<div>内容<div>这么简单,可以是任何自定义组件。

ok,对应react中实现对应的代码怎么写呢?

react基本插槽

使用插槽组件:

() => (
  <MainComp>
    <div>内容</div>
    {/* <MySub />  可以是任何自定义组件 */}
  </MainComp>
)

容器中定义插槽:

const MainComp = (props) => {
  return (
    <div class="main">
      {props.children ?? '后备信息,以防万一'}
    </div>
  )
}

react中,组件的子组件都存在props.children中,所以直接在jsx中渲染对应的位置渲染props.children变量就可以了,而后备内容可以应用任何js语法,来判断props.children收否存在,从而显示后备内容与否。

具名插槽

vue具名插槽

上面是最简单的场景,但有时候,一个容器需要渲染很多插槽,比如需要渲染一个内容区域content和一个底部区域footer,这时候就需要对插槽命名了,称之为具名插槽: 下面main-comp组件要使用两个插槽,分别命名为content和footer:

<template>
  <main-comp>
    <template v-slot:content>
      <sub-comp1></sub-comp1>
    </template>
    <template  v-slot:footer>
      <footer-comp></footer-comp>
    </template>
  </main-comp>
</template>

定义插槽:

<template>
  <div>
    <slot name="content"></slot>
    <slot name="footer"></slot>
  </div>
</template>

slot用name属性标注了其名字“xxx”,对应使用的时候要用v-slot:xxx,这样“xxx”的template就会对应替换成name是xxx的slot的位置。有一点需要注意,v-slot这个指令对template生效,也就是说,要使用具名插槽,必须要用template将内容包裹起来。

另外,如果slot显示指定name,其实它对应也是有name的,它默认的name叫default。

react具名插槽的讨论

具名插槽,对应react的话,我用了这些年react,还真没听过“具名插槽”这个称谓。不过尽管没有100%一致的对应vue的具名插槽,但类似的功能有几种实现方式:

模仿具名插槽

a. 用次序约定:

我们知道react的“插槽”写法(嵌套子组件),子组件都是作为props.children数组的子元素,那么其实最简单的一种方式就是,children的次序对应着某个子插槽。比如:
使用时:

() => {
  return (
    <MainComp>
      <SubComp1 />
      <FooterComp />
    </MainComp>
  )
}

那么,props.children[0]对应的就是SubComp1,而props.children[1]对应的就是FooterComp,所以在MainComp内部就可以这样:

const MainComp = (props) => {
  const content = props.children[0]
  const footer = props.children[1]

  return (
  <div>
    { content }
    { footer }
  </div>
  )
}

但是上述写法的问题在于:具名呢?说好的名称呢?使用的时候顺序乱了咋办?

b. 传递对象:

想要实现具名,可以下面这样写法,本质还是props.children,我们把对象作为“插槽”内容,对象的key就是插槽名称,value就是子组件:

() => {
  return (
    <MainComp>
      {
        {
          content:(<SubComp1 />),
          footer:(<FooterComp />)
        }
      }
    </MainComp>
  )
}

对应的MainComp:

const MainComp = (props) => {
  const { content, footer  } = props.children
  return (
  <div>
    { content }
    { footer }
  </div>
  )
}

这里这个props.children可以直接解构对象的属性。(有一点要注意的是:props.children不一定是数组,当只有一个元素的时候就不是数组)。

c. 判断组件的自定义静态,实现具名

上述这种写法看上去和vue的功能一致了,但是坦白讲,这样的代码在react世界里面实属罕见。不是说写法错误,但是似乎不那么符合使用习惯。

react中类似具名的插槽其实还可以通过给子组件显示命名方式实现,其实就是给子组件挂了个静态变量:

const Content = () => (<div>I am Content</div>)
const Footer = (props) => (<div>Footer Here {props.info}</div>)

// 两个子组件标记出来
Content.compName =  'content'
Footer.compName =  'footer'

这样在容器组件中就能通过这两个标记,识别出对应的组件:

export function MainComp(props) {

  const isMany = React.Children.count(props.children) > 0
  let footer = (<div>footer</div>)
  let content = (<div>content</div>)

  if (isMany) {
    React.Children.forEach(props.children, item => {
      const { compName } = item.type
      // 判断子组件类型
      if (compName === 'footer') footer = item
      if (compName === 'content') content = item
    })
  }

  return (
    <div>
      title text<br/>
      { content }
      { footer }
    </div>
  )
}

这里面用了几个React.Children的方法来判断props.children,核心逻辑是当children是数组时,遍历每一个子项,判断其“name”(这里我们的约定为compName),再根据name设置对应需要渲染的子组件的变量。

isMany部分也可以这样写:

  try {
    React.Children.only(children)
  } catch (e) {
    React.Children.forEach(children, item => {
      const { compName } = item.type
      if (compName === 'footer') footer = item
      if (compName === 'content') content = item
    })
  }

这样我们在使用“插槽”组件的时候就不用担心子组件次序问题了:

() => {
  return (
    <MainComp>
      <Footer /> {/* 先写footer还是能正确的渲染出来 */}
      <Content />
    </MainComp>
  )
}

上面a、b、c三种方法实现了和vue一样的具名的slot,第一种只是简单的次序对应,第二种能实现,但是不太符合react习惯,第三种能实现,也是react的写法。

不过,一般简单的需求,没必要用第三种,可以直接使用属性传递组件,我也不知道应该怎么叫,就叫属性插槽吧。

属性插槽

一个更符合react习惯,近似实现vue具名slot的方法是,直接传props,但props的类型是组件:

() => {
  return (
    <MainComp
      content={(<SubComp1 />)}
      footer={(<FooterComp />)}
    />
  )
}

这样在MainComp的内部直接通过props去拿对应的组件并渲染在适当的位置就行:

const MainComp = (props) => {
  const { content, footer } = props
  return (
  <div>
    { content }
    { footer }
  </div>
  )
}

严格意义上说,虽然这样能实现和vue具名插槽一样的功能,但使用却不是用插槽的形式。 但是这样代码在react世界中,却是最常见的方式。这可能是两种框架不同特点导致的微小差异了。

插槽传参

vue插槽传参

插槽传递参数,可以帮助我们实现一些更高级的功能。

先看下vue中,如何给插槽传递参数:

<template>
  <div class="main">
    <slot
      :styleProps="shareStyle"
      :data="shareStyle"
      :description="desc">
    </slot>
  </div>
</template>

上面的代码是定义slot时候,我们给slot绑定了三个属性,这看上去和我们使用组件,给组件传递参数的用法没有什么区别。

使用slot的时候这样接收参数:

  <!-- slotProps 是通过main传递过来的 -->
  <template v-slot:default="slotProps">
    <!-- 默认插槽可以简写成 v-slot -->
    <div :style="slotProps.styleProps">{{ slotProps.description}}</div>
  </template>

首先我们看到使用的时候在template标签中使用了指令v-slot,即v-slot:default="slotProps",这句话什么意思呢?

就是当前这里template对应default这个名称的slot(defalut可以省略,上面定义slot的地方也没写name=“default”,其实是省略了)。

而这个slotProps表示所有传递过来的属性,也就是说:

slotProps = {
  styleProps, data, description
}

所以在template中,可以使用slotProps上的任何属性。也可以给作为插槽的自定义组件传递参数, 比如:

<template v-slot:rightItem="slotProps">
  <staff :staff="slotProps.data"></staff>
</template>

vue插槽传参大致就是这样了,接着看看react吧。

react:render-props

react中其实没法直接给插槽传递参数,只能借助一点技术手段:函数。

这种方式有个专有名词叫:render-props。

render-props的具体的方式就是,子组件作为插槽是用函数的形式,而容器组件渲染的时候对应的就调用这个函数,在调用函数的时候,把需要传递的参数传入函数,这样在插槽函数的作用域内就拿到了数据:

() => {
  return (
    <MainComp>
      {
        (data) => (<Staff staff={data} />)
      }
    </MainComp>
  )
}

看下容器MainComp组件:

const MainComp = (props) => {

  const [data, setData] = useState({})
  useEffect(() => {
    const info = await getData()
    setData(info)
  }, [])
  return (
    <div>
      {
        props.children && props.children(data)
      }
    </div>
  )
}

当然了,这种函数形式的“插槽”不止可以用作插槽,用在普通的props上自然也是可以的。在react世界里,凡是要从另一个组件拿数据的场合,都可以考虑传个函数:

() => {
  return (
    <MainComp
      staff={(data) => (<Staff staff={data} />)}
    />
  )
}

对应的MainComp,最终也是通过函数调用给子组件传递参数,只是获取子组件的方式换一下:

const MainComp = (props) => {
  const [data, setData] = useState({})
  useEffect(() => {
    const info = await getData()
    setData(info)
  }, [])
  return (
    <div>
      {/* 这里变一下 */}
      {
        props.staff && props.staff(data)
      }
    </div>
  )
}

以上就是对两种框架“插槽”相关的实现方式的简单总结。

总言之,不管vue或者react,都有着很灵活的用法,上面的这些要素和技巧,都可以在实际项目中可以根据需要自行组合或者扩展。

到此这篇关于vue和react中关于插槽详解的文章就介绍到这了,更多相关vue react 插槽内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 使用react context 实现vue插槽slot功能

    首先来看下vue的slot的实现 <base-layout>组件,具名插槽name定义以及默认插槽 <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot n

  • React实现类似于Vue中的插槽的项目实践

    目录 搭建项目 实现方式1 实现方式2 最终效果展示 最近刚开始接触React,感觉React比Vue更灵活一些,但是感觉代码确实维护的时候可读性也没有Vue好(可能是因为我太菜了),Vue中很多都是自己的API, 看到这个api就知道想要实现的是什么功能,但是react 需要自己去读一下代码或者有好的代码注释习惯更好. 比如 Vue 中有一个插槽的概念,可以任意放置内容,那么灵活的 React要怎么实现这个功能呢,这篇文章主要就是记录一下“React实现类似于Vue中的插槽的效果” 搭建项目

  • vue和react中关于插槽详解

    目录 简述Slot 基本插槽 vue基本插槽 react基本插槽 具名插槽 vue具名插槽 react具名插槽的讨论 模仿具名插槽 属性插槽 插槽传参 vue插槽传参 react:render-props 简述Slot slot插槽是Vue对组件嵌套这种扩展机制的称谓,在react可以也这样称呼,但是并不很常见.不过叫slot确实很形象. 这样的形式就是slot插槽: vue <template> <container-comp> <content></conte

  • vue中的插槽详解

    vue中代码的复用, 为我们提供了 mixnis. 模板的复用, 为我们提供了 插槽( slot ) 插槽的分类 默认插槽 具名插槽 作用域插槽 当我们的组件中 我们只需要插入一个 html 标签的时候, 就使用默认插槽就可以了, 如果有多个, 我们就要给第一个 插槽取一个名字, 来决定到底插入哪一个插槽 当我们的插槽中要使用组件中的数据的时候, 就可能会用到作用域插槽 下面展示一下, 默认插槽的用法 使用时 以上就是默认插槽的使用 具名插槽, 也就是说我们在组件中定一个 多个 slot , 为

  • React中实现插槽效果的方案详解

    目录 React实现插槽 children实现插槽 props实现插槽 React实现插槽 在开发中,我们抽取了一个组件,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div.span等等这些元素. 我们应该让使用者可以决定某一块区域到底存放什么内容 这种需求在Vue当中有一个固定的做法是通过slot来完成的,React呢? 在React中是没有插槽的概念的, 或者说在React中是不需要插槽的, 因为React对于这种需要插槽的情况非常灵活,有两种方案可以实现: 组件的

  • React Diff算法不采用Vue的双端对比原因详解

    目录 前言 React 官方的解析 Fiber 的结构 Fiber 链表的生成 React 的 Diff 算法 第一轮,常见情况的比对 第二轮,不常见的情况的比对 重点如何协调更新位置信息 小结 图文解释 React Diff 算法 最简单的 Diff 场景 复杂的 Diff 场景 Vue3 的 Diff 算法 第一轮,常见情况的比对 第二轮,复杂情况的比对 Vue2 的 Diff 算法 第一轮,简单情况的比对 第二轮,不常见的情况的比对 React.Vue3.Vue2 的 Diff 算法对比

  • bing Map 在vue项目中的使用详解

    写在最前面 拥有全球数据库国内好像就只有百度地图有,高德.搜狗.腾讯的都不行,但是由于百度地图的数据更新不及时,所以在做相关项目要用到国外数据的时候,最好还是推荐使用bingMap. bing Map 使用教程(基础) 参考文档:bing Map 官方教程 bing Map 初始化 引入bing map资源 <script type='text/javascript' src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap&k

  • Vue中props的详解

    看一下官方文档: 组件实例的作用域是孤立的.这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据.父组件的数据需要通过 prop 才能下发到子组件中. 也就是props是子组件访问父组件数据的唯一接口. 详细一点解释就是: 一个组件可以直接在模板里面渲染data里面的数据(双大括号). 子组件不能直接在模板里面渲染父元素的数据. 如果子组件想要引用父元素的数据,那么就在prop里面声明一个变量(比如a),这个变量就可以引用父元素的数据.然后在模板里渲染这个变量(前面的a),这时候渲染

  • vue中组件通信详解(父子组件, 爷孙组件, 兄弟组件)

    vue中我们常常用到组件. 那么组件总体可以分为如下的几种关系. 父子组件, 爷孙组件, 兄弟组件. 这几种组件之间是如何通信的呢? 父子组件通信 根据vue中的文档可知, 组件的props属性用于接收父组件传递的信息. 而子组件想要向父组件传递信息, 可以使用$emit事件. 我们定义两个组件, 一个为父组件名为father, 另外一个为子组件child. 子组件通过props属性接收父组件传递的值, 这个值为fname, 是父组件的名字. 点击子组件的按钮, 触发toFather事件, 向父

  • vue3中vue.config.js配置及注释详解

    目录 报错 打包时提示文件过大,配置解决方案,如下 总结 报错 asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).This can impact web performance.entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit

  • Vue面试中observable原理详解

    目录 一.Observable 是什么 二.使用场景 三.原理分析 一.Observable 是什么 Observable 翻译过来我们可以理解成可观察的 我们先来看一下其在Vue中的定义 Vue.observable,让一个对象变成响应式数据.Vue 内部会用它来处理 data 函数返回的对象 返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新.也可以作为最小化的跨组件状态存储器 Vue.observable({ count : 1}) 其作用等同于 new vue(

  • Vue的实例、生命周期与Vue脚手架(vue-cli)实例详解

    一.Vue的实例 1.1.创建一个 Vue 的实例 每个 Vue 应用都是通过 Vue 函数创建一个新的 Vue 实例开始的: var vm = new Vue({// 选项}) 虽然没有完全遵循 MVVM 模型,Vue 的设计无疑受到了它的启发.因此在文档中经常会使用 vm (ViewModel 的简称) 这个变量名表示 Vue 实例. 1.vue.js就是一个构造器,通过构造器Vue来实例化一个对象:例如:var vm = new Vue({}); 2.实例化Vue时,需要传入一个参数(选项

随机推荐