React动态添加标签组件
背景
在前端开发的过程中,一些表单的输入经常需要输入多个内容,如果采用一个输入框+逗号分隔的方式,展示起来不是很清晰,一般需要采用标签的方式
需求


可以指定空状态时的标题
设置标签颜色
每个标签的最大长度(字符数)
接口传递的时候的分隔标记(是用逗号,还是其他)
直接处理表单,不需要二次处理
所以需要传入以下内容给该组件
title:标题
separator:分隔标记
maxLength:最大长度
color:颜色
form,name:处理的表单和对应的字段
const { title = '新增一个', separator = ',', maxLength = 40, color = 'orange', form, name } = props;TagInput.propTypes = {title: PropTypes.string, // 新增一个tag的标题separator: PropTypes.string, // 分隔符maxLength: PropTypes.number, // tag最大长度color: PropTypes.string, // tag颜色form: PropTypes.object, // formkey: PropTypes.string, // form的key
}; 代码编写
是否显示输入框
首先需要有一个虚线框的标签
<Tag style={{ background: '#fff', borderStyle: 'dashed' }}><PlusOutlined /> {title}
</Tag> 点击后出现文本输入框
<Input type="text" size="small" style={{ width: 78 }} /> 并且锚定这个输入框(出现输入光标)
所以需要有一个状态记录是否显示输入框
const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框 所以上述代码变为:
const saveInputRef = useRef();useEffect(() => {if (inputVisible) saveInputRef.current.focus();
}, [inputVisible]);{inputVisible && (<Input ref={saveInputRef} type="text" size="small" style={{ width: 78 }} />
)}
{!inputVisible && (<Tag onClick={() => setInputVisible(true)} style={{ background: '#fff', borderStyle: 'dashed' }}><PlusOutlined /> {title}</Tag>
)} useEffect监听输入框是否出现,如果出现,则锚定「saveInputRef.current.focus()」
添加一个标签
为了记录输入框的内容定义一个新的变量
const [inputValue, setInputValue] = useState(''); // 输入框的值<Input ref={saveInputRef} type="text" size="small" style={{ width: 78 }} value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> 每次输入内容都会修改inputValue的值
因为有多个标签,先定义一个变量来记录我们已经添加的标签
const [tags, setTags] = useState([]); // 待分隔列表 当鼠标在输入框外部点击或者敲击回车的时候,都需要添加一个标签
所以需要给输入框添加onBlur和onPressEnter方法
<Inputref={saveInputRef}type="text"size="small"style={{ width: 78 }}value={inputValue}onChange={(e) => setInputValue(e.target.value)}onBlur={handleInputConfirm}onPressEnter={handleInputConfirm}
/> 编写添加标签的方法:handleInputConfirm
拿到之前的标签+本次输入的,一起放到tags变量中
给表单设置一下这个值(用分隔标记拼接起来)
隐藏输入框
清空输入框
/** 新增一个tag* */
const handleInputConfirm = () => {if (inputValue && tags.indexOf(inputValue) === -1) {const newTags = [...tags, inputValue];setTags(newTags);form.setFieldsValue({ [name]: newTags?.join(separator) });} else {message.error('请正确输入');}setInputVisible(false);setInputValue('');
}; 展示标签
在上述步骤之后,tags中已经添加了我们的标签了,将它展示出来
判断字符串长度,如果大于我们配置的最大长度则裁剪,没有则全部展示
超长的标签增加一个气泡提示,鼠标移动上去后可以看到全部内容
{tags.map((tag) => {const isLongTag = tag.length > maxLength;const tagElem = (<Tag key={tag} color={color}>{isLongTag ? `${tag.slice(0, maxLength)}...` : tag}</Tag>);return isLongTag ? (<Tooltip title={tag} key={tag}>{tagElem}</Tooltip>) : (tagElem);
})} 删除标签
给Tag设置closable和onClose方法
const tagElem = (<Tag key={tag} closable onClose={() => handleClose(tag)} color={color}>{isLongTag ? `${tag.slice(0, 20)}...` : tag}</Tag>
); handleClose方法:
过滤tags中不需要的tag并更新
重新给表单对应的键值对赋值
/** 删除某个tag* */
const handleClose = (removedTag) => {const updatedTags = tags.filter((tag) => tag !== removedTag);setTags(updatedTags);form.setFieldsValue({ [name]: updatedTags?.join(separator) });
}; 编辑状态
当我们处于编辑状态的时候,打开表单后,它原本就有内容了
监听一下表单的内容,如果存在,则使用分隔标记分隔后塞入tags中
useEffect(() => {if (form.getFieldValue(name)) setTags(form.getFieldValue(name).split(separator));}, [form.getFieldValue(name)]); Antd4.x完整代码
折叠源码
import React, { memo, useEffect, useRef, useState } from 'react';
import { Input, message, Tag, Tooltip } from 'antd';
import PropTypes from 'prop-types';
import { PlusOutlined } from '@ant-design/icons';/** tag形式分隔* */
const TagInput = memo((props) => {const [tags, setTags] = useState([]); // 待分隔列表const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框const [inputValue, setInputValue] = useState(''); // 输入框的值const { title = '新增一个', separator = ',', maxLength = 40, color = 'orange', form, name } = props;const saveInputRef = useRef();useEffect(() => {if (inputVisible) saveInputRef.current.focus();}, [inputVisible]);useEffect(() => {if (form.getFieldValue(name)) setTags(form.getFieldValue(name).split(separator));}, [form.getFieldValue(name)]);/** 删除某个tag* */const handleClose = (removedTag) => {const updatedTags = tags.filter((tag) => tag !== removedTag);setTags(updatedTags);form.setFieldsValue({ [name]: updatedTags?.join(separator) });};/** 新增一个tag* */const handleInputConfirm = () => {if (inputValue && tags.indexOf(inputValue) === -1) {const newTags = [...tags, inputValue];setTags(newTags);form.setFieldsValue({ [name]: newTags?.join(separator) });} else {message.error('请正确输入');}setInputVisible(false);setInputValue('');};return (<>{tags.map((tag) => {const isLongTag = tag.length > maxLength;const tagElem = (<Tag key={tag} closable onClose={() => handleClose(tag)} color={color}>{isLongTag ? `${tag.slice(0, 20)}...` : tag}</Tag>);return isLongTag ? (<Tooltip title={tag} key={tag}>{tagElem}</Tooltip>) : (tagElem);})}{inputVisible && (<Inputref={saveInputRef}type="text"size="small"style={{ width: 78 }}value={inputValue}onChange={(e) => setInputValue(e.target.value)}onBlur={handleInputConfirm}onPressEnter={handleInputConfirm}/>)}{!inputVisible && (<Tag onClick={() => setInputVisible(true)} style={{ background: '#fff', borderStyle: 'dashed' }}><PlusOutlined /> {title}</Tag>)}</>);
});TagInput.propTypes = {title: PropTypes.string, // 新增一个tag的标题separator: PropTypes.string, // 分隔符maxLength: PropTypes.number, // tag最大长度color: PropTypes.string, // tag颜色form: PropTypes.object, // formkey: PropTypes.string, // form的key
};export default TagInput; Antd3.x完整代码
antd3.x中部分组件的用法不一样,需要修改一下
折叠源码
import React, { useEffect, useRef, useState } from 'react';
import { Icon, Input, message, Tag, Tooltip } from 'antd';
import PropTypes from 'prop-types';/** tag形式分隔* */
const TagInput = React.forwardRef((props, ref) => {const [tags, setTags] = useState([]); // 待分隔列表const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框const [inputValue, setInputValue] = useState(''); // 输入框的值const {title = '新增一个',separator = ',',maxLength = 40,color = 'orange',form,name,} = props;const saveInputRef = useRef();useEffect(() => {if (inputVisible) saveInputRef.current.focus();}, [inputVisible]);useEffect(() => {if (form.getFieldValue(name)) {setTags(form.getFieldValue(name).split(separator));}}, [form.getFieldValue(name)]);/** 删除某个tag* */const handleClose = (removedTag) => {const updatedTags = tags.filter((tag) => tag !== removedTag);setTags(updatedTags);form.setFieldsValue({ [name]: updatedTags?.join(separator) });};/** 新增一个tag* */const handleInputConfirm = () => {if (inputValue && tags.indexOf(inputValue) === -1) {const newTags = [...tags, inputValue];setTags(newTags);form.setFieldsValue({ [name]: newTags?.join(separator) });} else {message.error('请正确输入');}setInputVisible(false);setInputValue('');};return (<>{tags.map((tag) => {const isLongTag = tag.length > maxLength;const tagElem = (<Tagkey={tag}closableonClose={() => handleClose(tag)}color={color}>{isLongTag ? `${tag.slice(0, 20)}...` : tag}</Tag>);return isLongTag ? (<Tooltip title={tag} key={tag}>{tagElem}</Tooltip>) : (tagElem);})}{inputVisible && (<Inputref={saveInputRef}type="text"size="small"style={{ width: 78 }}value={inputValue}onChange={(e) => setInputValue(e.target.value)}onBlur={handleInputConfirm}onPressEnter={handleInputConfirm}/>)}{!inputVisible && (<TagonClick={() => setInputVisible(true)}style={{ background: '#fff', borderStyle: 'dashed' }}><Icon type="plus-circle" /> {title}</Tag>)}</>);
});TagInput.propTypes = {title: PropTypes.string, // 新增一个tag的标题separator: PropTypes.string, // 分隔符maxLength: PropTypes.number, // tag最大长度color: PropTypes.string, // tag颜色form: PropTypes.object, // formkey: PropTypes.string, // form的key
};export default TagInput;
相关文章:
React动态添加标签组件
背景 在前端开发的过程中,一些表单的输入经常需要输入多个内容,如果采用一个输入框逗号分隔的方式,展示起来不是很清晰,一般需要采用标签的方式 需求 可以指定空状态时的标题设置标签颜色每个标签的最大长度(字符数)接口传递的时候的分隔标记(是用逗号,还是其他)直接处理表单,不…...
[Linux]套接字通信
摘于https://subingwen.cn,作者:苏丙榅 侵删 文章目录 1. 套接字-socket1.1 概念1.2 网络协议1.3 socket编程1.3.1 字节序1.3.2 IP地址转换1.3.3 sockaddr 数据结构1.3.4 套接字函数 1.4 TCP通信流程1.4.1 服务器端通信流程1.4.2 客户端的通信流程 1.5 扩展阅读1.5.1 初始化套…...
MySQL的故事——MySQL架构与历史
MySQL架构与历史 文章目录 MySQL架构与历史一、MySQL逻辑架构二、并发控制三、事务四、多版本并发控制(MVCC) 一、MySQL逻辑架构 第一层:连接处理、授权认证、安全等等 第二层:查询解析、分析、优化、缓存以及所有的内置函数。包含跨存储引擎的功能&…...
手写Mybatis:第12章-完善ORM框架,增删改查操作
文章目录 一、目标:完善增删改查二、设计:完善增删改查三、实现:完善增删改查3.1 工程结构3.2 完善增删改查类图3.3 扩展解析元素3.4 新增执行方法3.4.1 执行器接口添加update3.4.2 执行器抽象基类3.4.3 简单执行器 3.5 语句处理器实现3.5.1 …...
【1】DDR---容量计算
1、容量计算 density:芯片容量,bit为单位 depth:地址空间, width:数据位宽 densitydepth*width 2、三星DDR 4Gbit(总容量)256M(地址空间)*16(位宽ÿ…...
YashanDB:潜心实干,数据库核心技术突破没有捷径可走
都说数据库是三大基础软件中的一块硬骨头,技术门槛高、研发周期长、工程要求高,市场长期被几大巨头所把持。 因此,实现突破一直是中国数据库产业的夙愿。自上个世纪80年代起,中国数据库产业走过艰辛坎坷的四十余载,终…...
Talk | ICCV‘23南洋理工大学博士后李祥泰:面向统一高效的视频分割方法设计
本期为TechBeat人工智能社区第528期线上Talk! 北京时间9月6日(周三)20:00,南洋理工大学博士后研究员—李祥泰的Talk已准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “面向统一高效的视频分割方法设计”,他分享了其在视…...
怎样把英语视频字幕翻译成中文
我们知道,随着中外文化交流日益频繁,视频翻译作为一种重要的跨文化交流方式,也越来越受到重视。那么,怎样把英语视频翻译成中文,北京视频翻译哪里比较专业? 据了解,视频翻译是直接将一种语言的音…...
智慧铁路:机车整备场数字孪生
机车整备场是铁路运输系统中的重要组成部分,它承担着机车的维修、保养和整备工作,对保障铁路运输的运维和安全起着至关重要的作用。 随着铁路运输的发展、机车技术的不断进步,以及数字化转型的不断推进,数字孪生技术在机车整备场…...
ImageSharp.Web实战:轻松搭建高效图片服务
很多情况下,在开发如PC、H5、小程序等综合平台的时候,图片的展示是个比较头疼的问题。尤其是有会员功能,会员可以上传图片的平台,更是一件麻烦事。平台展示图片的地方,尺寸是定义好的。但用户不配合,上传的…...
端口扫描-安全体系-网络安全技术和协议
端口扫描-安全体系-网络安全技术和协议 端口扫描信息安全的保证体系和评估方法网络安全技术网络攻击和威胁(重要)网络安全协议 端口扫描 全TCP连接:三次握手 半打开式扫描:前两次握手 FIN扫描:不用建立TCP连接 第三方扫描: 拒绝服务攻击有: 同步包风暴ICMP攻击SNMP攻击 都是修改…...
C# wpf 实现截屏框热键截屏功能
wpf截屏系列 第一章 使用GDI实现截屏 第二章 使用DockPanel制作截屏框 第三章 实现截屏框热键截屏(本章) 第四章 实现截屏框实时截屏 第五章 使用ffmpeg命令行实现录屏 文章目录 wpf截屏系列前言一、实现步骤1、响应热键2、截屏显示(1&#…...
springboot + activiti实现activiti微服务化
概述 本文介绍如何将springbootactiviti进行整合,并配合eureka,zuul和feign实现activiti的微服务化,将流程控制和业务逻辑分离. 并实现了几个比较特殊的功能,比如时间段委托(某人请假或出差,出差时间内,所有待办交给被委托人处理),比如节点的无限级加签功能(流程本身有不确定性…...
c语言练习41:深入理解字符串函数strlen strcpy strcat
深入理解字符串函数strlen strcpy strcat 模拟实现:”strlen strcpy strcat strlen strcat: #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<assert.h> strlen 1.通过指针移动模拟 //int my_strlen(char* str) { // size_t c…...
Vue3+Vue-i18n+I18N ALLY+VSCODE 自动翻译多国语言
ps: 效果图放前面,符合的往下看,不符合的出门右转,希望多多点赞评论支持。 三种语言模式,分别是中文、英文、日文 批量翻译 最后的结果 配置vue-i18n 1、下载安装vue-i18n,9以上的版本。 2、创建对应文件夹 3、对应文件夹中代…...
idea意外退出mac
目录 问题描述 解决过程 问题描述 mac上的idea我很久没用了,之前用的时候还是发布新版的开源项目,这几天再用的时候,就出现了idea意外退出的问题,我上网查找了很久,对于我的问题都没有很好的解决。 解决过程 在寻求…...
百度智能云千帆大模型平台2.0来了!从大模型到生产力落地的怪兽级平台!!
目录 前言 最佳算力效能为企业降低门槛 最多大模型,最多数据集为企业保驾护航 企业级安全对于企业来说是硬性要求 前言 普通人或许感知不明显,但是对于企业而言,身处AI时代,是否选择投资大模型,是否拥抱人工智能…...
k8s nfs-client 添加挂载参数 —— 筑梦之路
背景介绍 为什么要使用noresvport参数挂载NAS?不重新挂载会有什么后果? 如果发生网络切换或者后端服务的HA倒换,小概率会造成NFS文件系统阻塞,那就可能需要几分钟时间连接才会自动恢复,极端情况下甚至需要重启ECS才能恢…...
【算法】堆排序 详解
堆排序 详解 堆排序代码实现 排序: 排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性: 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,…...
解决Maven依赖下载问题:从阿里云公共仓库入手
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)
+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...
