实现一个vue3组件库 - partial挑选

pid: 109362778

引言

当我们在实现某个组件需要获取到外部传入的slot的引用、或者希望slot是单个元素时,通常会包裹一层元素(比如div)通过给外层元素打上ref标记来间接获取,例如:

vue 复制代码
<template>
    <divider ref="container">
       <slot></slot>
    </divider>


</template>

缺点很明显,加了一层元素破坏了原本的html结构。

假设slot本身就是单个元素,比如在使用Popconfirm 气泡确认框 | Element Plus 时,就会默认使得reference slot是单个元素.

但是当我们传入多个元素时, ELPopconfirm也能在不添加外层div的情况下,实现效果:

xml 复制代码
<el-popconfirm title="Are you sure to delete this?">
 <template #reference>
  <el-button>Delete</el-button>
  <el-button>Delete</el-button>
 </template>
</el-popconfirm>

可以看到我们传入的reference元素时两个按钮,但是实际只渲染了第一个元素,也就是从传入的slot中挑选出了第一个元素作为reference

为此,我们可以尝试实现一个抽象组件partial来获取第一个元素。

实现思路

一个基本的抽象组件如下:

vue 复制代码
export default defineComponent({
    setup(props, context) {
       const slots = context.slots;
       
       //返回的是一个render函数用来代替template, 
       //h函数用来创建VNode
       return () => {
        return h('div',{}, [...slots.default()])
       }

    },

})

上面的代码将传入的slot套上一层div后返回.

slot.defalut是一个函数,他的返回值可以简单理解为根据你传入的模板而生成的VNode[], 因此我们需要做文章的地方也就是这个返回值了.

  • 通过slot.default获取到VNode\[\],并获取它的第一个VNode

    ts 复制代码
    let dft = slots.default && slots.default()[0];
  • 判断dft的type是否是SYmbol, 一些特殊的VNode节点(template, slot, txt ,comment)的type是symbol , 我们要做的就是排除template,slot,comment节点

    ini 复制代码
    // 排除template slot comment 节点
    while (typeof dft!.type === 'symbol') {
        // 文本节点
        if (dft!.type === Symbol.for('v-txt')){
           dft  = h('span',{},[dft]);
           break;
        }
        // 不允许第一个元素是注释节点
        if (dft!.type === Symbol.for('v-cmt')){
           dft  = h('div',{reason:'not allowed', result:'force replacement with div'});
           warn('The first child node of SPartial is a text (annotation) node, which is not allowed');
           break;
        }
        //如果是template slot节点的话,获取它的第一个孩子节点
        dft = (dft as any)!.children[0];
    
    }
  • 最后获取到第一个元素节点后返回即可

    ts 复制代码
    return () => {
        return h(dft as any, {...attr})
    }

完整逻辑

xml 复制代码
<script lang="ts">

import {defineComponent, h, useAttrs, warn} from "vue";

/**
 * @description 取default slot中的第一个非(template slot comment)的组件节点
 */
export default defineComponent({
    setup(__, context) {
       const slots = context.slots;
       const attr = useAttrs();
       let dft = slots.default && slots.default()[0];

       // 没传
       if (!dft) {
          warn('SPartial has empty slot');
       }


       // 排除template slot comment 节点
       while (typeof dft!.type === 'symbol') {
          // 文本节点
          if (dft!.type === Symbol.for('v-txt')){
             dft  = h('span',{},[dft]);
             break;
          }
          // 不允许第一个元素是注释节点
          if (dft!.type === Symbol.for('v-cmt')){
             dft  = h('div',{reason:'not allowed', result:'force replacement with div'});
             warn('The first child node of SPartial is a text (annotation) node, which is not allowed');
             break;
          }
          dft = (dft as any)!.children[0];

       }


       return () => {
          return h(dft as any, {...attr})
       }


    },
    name: 'SPartial',


})
</script>

最终效果

xml 复制代码
<SPartial>
    hello
    <s-button>hello world!</s-button>
</SPartial>
xml 复制代码
<SPartial>
    <template #default>
       <s-button>hello world!</s-button>
       <s-button>hello world!</s-button>
    </template>
</SPartial>
xml 复制代码
<SPartial>
    <!--      1111-->
    1
    <s-button>hello world!</s-button>
    <s-button>hello world!</s-button>
</SPartial>

写在最后

这里只是提供前言所述问题的一个解决方案, 代码可能不严谨和全面, 欢迎指出,一起掉头发(

这个项目的地址是:lastertd/sss-ui-plus: 适用于vue3的组件库 (github.com)在这里求一个star✨

感谢看到最后💟💟💟

相关推荐
wflynn12 分钟前
GitHub 今日推荐|dquickts:能暂停、能存档的 QuickJS
开源·github·c·durable-execution·embeddable·hibernation
Bigger18 分钟前
PR 没被合并,我把 Tauri 版自己做出来了:安装包从 100MB 砍到 33MB
开源·ai编程·掘金社区
hudie76528 分钟前
LikeShop 二开最常改的 5 个文件(附改前改后对比)
前端·uni-app·php·个人开发
qetfw1 小时前
Bash 函数与流程控制:read、if、for、case 验证
linux·前端·javascript·bash
文慧的科技江湖1 小时前
2026年10月24日智慧农业行业早报:模型数到175个,棚里的账还按年算
开源·智慧农业·农业物联网·数字乡村·1024程序员节·农业机器人·农业数字化
jason.zeng@15022071 小时前
Nginx 配置详解(静态 HTML、下载目录、API 反向代理三套场景)
java·vue.js·1024程序员节
鬼手点金1 小时前
从 API 消息到模型 Token:Chat Template
java·前端·javascript·数据库·人工智能·深度学习·1024程序员节
Shadow(⊙o⊙)2 小时前
LangChain 自定义工具全解析:从 @tool 到 bind_tools 的完整调用链路
java·服务器·前端·1024程序员节
TTATTC2 小时前
为什么 Python 做 Agent 总在并发中崩溃?拆解 Blockcell 在 Rust 下的 Host-Cell 隔离与自愈状态机
docker·开源
杨白白_yangbaibai2 小时前
我开源了个免费的 Markdown 编辑器:Lumin
开源