• React动态添加标签组件


    背景

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

    需求

    0ff25462ff2bcc1c8dc03967aa615ea1.png

    b33aef65285c5b98976a635d81323344.png

    1. 可以指定空状态时的标题

    2. 设置标签颜色

    3. 每个标签的最大长度(字符数)

    4. 接口传递的时候的分隔标记(是用逗号,还是其他)

    5. 直接处理表单,不需要二次处理

    所以需要传入以下内容给该组件

    • title:标题

    • separator:分隔标记

    • maxLength:最大长度

    • color:颜色

    • form,name:处理的表单和对应的字段

    1. const { title = '新增一个', separator = ',', maxLength = 40, color = 'orange', form, name } = props;
    2. TagInput.propTypes = {
    3.   title: PropTypes.string// 新增一个tag的标题
    4.   separator: PropTypes.string// 分隔符
    5.   maxLength: PropTypes.number, // tag最大长度
    6.   color: PropTypes.string// tag颜色
    7.   form: PropTypes.object, // form
    8.   key: PropTypes.string// form的key
    9. };

    代码编写

    是否显示输入框

    首先需要有一个虚线框的标签

    1. '#fff', borderStyle: 'dashed' }}>
    2.      {title}

    点击后出现文本输入框

    type="text" size="small" style={{ width: 78 }} />

    并且锚定这个输入框(出现输入光标)

    所以需要有一个状态记录是否显示输入框

    const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框

    所以上述代码变为:

    1. const saveInputRef = useRef();
    2. useEffect(() => {
    3.   if (inputVisible) saveInputRef.current.focus();
    4. }, [inputVisible]);
    5. {inputVisible && (
    6.   type="text" size="small" style={{ width: 78 }} />
    7. )}
    8. {!inputVisible && (
    9.    setInputVisible(true)} style={{ background: '#fff', borderStyle: 'dashed' }}>
    10.      {title}
    11.   
    12. )}

    useEffect监听输入框是否出现,如果出现,则锚定「saveInputRef.current.focus()」

    添加一个标签

    为了记录输入框的内容定义一个新的变量

    1. const [inputValue, setInputValue] = useState(''); // 输入框的值
    2. type="text" size="small" style={{ width: 78 }} value={inputValue} onChange={(e) => setInputValue(e.target.value)} />

    每次输入内容都会修改inputValue的值

    因为有多个标签,先定义一个变量来记录我们已经添加的标签

    const [tags, setTags] = useState([]); // 待分隔列表

    当鼠标在输入框外部点击或者敲击回车的时候,都需要添加一个标签

    所以需要给输入框添加onBlur和onPressEnter方法

    1.   ref={saveInputRef}
    2.   type="text"
    3.   size="small"
    4.   style={{ width: 78 }}
    5.   value={inputValue}
    6.   onChange={(e) => setInputValue(e.target.value)}
    7.   onBlur={handleInputConfirm}
    8.   onPressEnter={handleInputConfirm}
    9. />

    编写添加标签的方法:handleInputConfirm

    • 拿到之前的标签+本次输入的,一起放到tags变量中

    • 给表单设置一下这个值(用分隔标记拼接起来)

    • 隐藏输入框

    • 清空输入框

    1. /*
    2.  * 新增一个tag
    3.  * */
    4. const handleInputConfirm = () => {
    5.   if (inputValue && tags.indexOf(inputValue) === -1) {
    6.     const newTags = [...tags, inputValue];
    7.     setTags(newTags);
    8.     form.setFieldsValue({ [name]: newTags?.join(separator) });
    9.   } else {
    10.     message.error('请正确输入');
    11.   }
    12.   setInputVisible(false);
    13.   setInputValue('');
    14. };

    展示标签

    在上述步骤之后,tags中已经添加了我们的标签了,将它展示出来

    • 判断字符串长度,如果大于我们配置的最大长度则裁剪,没有则全部展示

    • 超长的标签增加一个气泡提示,鼠标移动上去后可以看到全部内容

    1. {tags.map((tag) => {
    2.   const isLongTag = tag.length > maxLength;
    3.   const tagElem = (
    4.     
    5.       {isLongTag ? `${tag.slice(0, maxLength)}...` : tag}
    6.     
    7.   );
    8.   return isLongTag ? (
    9.     
    10.       {tagElem}
    11.     
    12.   ) : (
    13.     tagElem
    14.   );
    15. })}

    删除标签

    给Tag设置closable和onClose方法

    1. const tagElem = (
    2.    handleClose(tag)} color={color}>
    3.     {isLongTag ? `${tag.slice(0, 20)}...` : tag}
    4.   
    5. );

    handleClose方法:

    • 过滤tags中不需要的tag并更新

    • 重新给表单对应的键值对赋值

    1. /*
    2.  * 删除某个tag
    3.  * */
    4. const handleClose = (removedTag) => {
    5.   const updatedTags = tags.filter((tag) => tag !== removedTag);
    6.   setTags(updatedTags);
    7.   form.setFieldsValue({ [name]: updatedTags?.join(separator) });
    8. };

    编辑状态

    当我们处于编辑状态的时候,打开表单后,它原本就有内容了

    监听一下表单的内容,如果存在,则使用分隔标记分隔后塞入tags中

    1. useEffect(() => {
    2.     if (form.getFieldValue(name)) setTags(form.getFieldValue(name).split(separator));
    3.   }, [form.getFieldValue(name)]);

    Antd4.x完整代码

    折叠源码

    1. import React, { memo, useEffect, useRef, useState } from 'react';
    2. import { Input, message, Tag, Tooltip } from 'antd';
    3. import PropTypes from 'prop-types';
    4. import { PlusOutlined } from '@ant-design/icons';
    5. /*
    6.  * tag形式分隔
    7.  * */
    8. const TagInput = memo((props) => {
    9.   const [tags, setTags] = useState([]); // 待分隔列表
    10.   const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框
    11.   const [inputValue, setInputValue] = useState(''); // 输入框的值
    12.   const { title = '新增一个', separator = ',', maxLength = 40, color = 'orange', form, name } = props;
    13.   const saveInputRef = useRef();
    14.   useEffect(() => {
    15.     if (inputVisible) saveInputRef.current.focus();
    16.   }, [inputVisible]);
    17.   useEffect(() => {
    18.     if (form.getFieldValue(name)) setTags(form.getFieldValue(name).split(separator));
    19.   }, [form.getFieldValue(name)]);
    20.   /*
    21.    * 删除某个tag
    22.    * */
    23.   const handleClose = (removedTag) => {
    24.     const updatedTags = tags.filter((tag) => tag !== removedTag);
    25.     setTags(updatedTags);
    26.     form.setFieldsValue({ [name]: updatedTags?.join(separator) });
    27.   };
    28.   /*
    29.    * 新增一个tag
    30.    * */
    31.   const handleInputConfirm = () => {
    32.     if (inputValue && tags.indexOf(inputValue) === -1) {
    33.       const newTags = [...tags, inputValue];
    34.       setTags(newTags);
    35.       form.setFieldsValue({ [name]: newTags?.join(separator) });
    36.     } else {
    37.       message.error('请正确输入');
    38.     }
    39.     setInputVisible(false);
    40.     setInputValue('');
    41.   };
    42.   return (
    43.     <>
    44.       {tags.map((tag) => {
    45.         const isLongTag = tag.length > maxLength;
    46.         const tagElem = (
    47.            handleClose(tag)} color={color}>
    48.             {isLongTag ? `${tag.slice(0, 20)}...` : tag}
    49.           
    50.         );
    51.         return isLongTag ? (
    52.           
    53.             {tagElem}
    54.           
    55.         ) : (
    56.           tagElem
    57.         );
    58.       })}
    59.       {inputVisible && (
    60.         
    61.           ref={saveInputRef}
    62.           type="text"
    63.           size="small"
    64.           style={{ width: 78 }}
    65.           value={inputValue}
    66.           onChange={(e) => setInputValue(e.target.value)}
    67.           onBlur={handleInputConfirm}
    68.           onPressEnter={handleInputConfirm}
    69.         />
    70.       )}
    71.       {!inputVisible && (
    72.          setInputVisible(true)} style={{ background: '#fff', borderStyle: 'dashed' }}>
    73.            {title}
    74.         
    75.       )}
    76.     
    77.   );
    78. });
    79. TagInput.propTypes = {
    80.   title: PropTypes.string// 新增一个tag的标题
    81.   separator: PropTypes.string// 分隔符
    82.   maxLength: PropTypes.number, // tag最大长度
    83.   color: PropTypes.string// tag颜色
    84.   form: PropTypes.object, // form
    85.   key: PropTypes.string// form的key
    86. };
    87. export default TagInput;

    Antd3.x完整代码

    antd3.x中部分组件的用法不一样,需要修改一下

    折叠源码

    1. import React, { useEffect, useRef, useState } from 'react';
    2. import { Icon, Input, message, Tag, Tooltip } from 'antd';
    3. import PropTypes from 'prop-types';
    4. /*
    5.  * tag形式分隔
    6.  * */
    7. const TagInput = React.forwardRef((props, ref) => {
    8.   const [tags, setTags] = useState([]); // 待分隔列表
    9.   const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框
    10.   const [inputValue, setInputValue] = useState(''); // 输入框的值
    11.   const {
    12.     title = '新增一个',
    13.     separator = ',',
    14.     maxLength = 40,
    15.     color = 'orange',
    16.     form,
    17.     name,
    18.   } = props;
    19.   const saveInputRef = useRef();
    20.   useEffect(() => {
    21.     if (inputVisible) saveInputRef.current.focus();
    22.   }, [inputVisible]);
    23.   useEffect(() => {
    24.     if (form.getFieldValue(name)) {
    25.       setTags(form.getFieldValue(name).split(separator));
    26.     }
    27.   }, [form.getFieldValue(name)]);
    28.   /*
    29.    * 删除某个tag
    30.    * */
    31.   const handleClose = (removedTag) => {
    32.     const updatedTags = tags.filter((tag) => tag !== removedTag);
    33.     setTags(updatedTags);
    34.     form.setFieldsValue({ [name]: updatedTags?.join(separator) });
    35.   };
    36.   /*
    37.    * 新增一个tag
    38.    * */
    39.   const handleInputConfirm = () => {
    40.     if (inputValue && tags.indexOf(inputValue) === -1) {
    41.       const newTags = [...tags, inputValue];
    42.       setTags(newTags);
    43.       form.setFieldsValue({ [name]: newTags?.join(separator) });
    44.     } else {
    45.       message.error('请正确输入');
    46.     }
    47.     setInputVisible(false);
    48.     setInputValue('');
    49.   };
    50.   return (
    51.     <>
    52.       {tags.map((tag) => {
    53.         const isLongTag = tag.length > maxLength;
    54.         const tagElem = (
    55.           
    56.             key={tag}
    57.             closable
    58.             onClose={() => handleClose(tag)}
    59.             color={color}
    60.           >
    61.             {isLongTag ? `${tag.slice(0, 20)}...` : tag}
    62.           
    63.         );
    64.         return isLongTag ? (
    65.           
    66.             {tagElem}
    67.           
    68.         ) : (
    69.           tagElem
    70.         );
    71.       })}
    72.       {inputVisible && (
    73.         
    74.           ref={saveInputRef}
    75.           type="text"
    76.           size="small"
    77.           style={{ width: 78 }}
    78.           value={inputValue}
    79.           onChange={(e) => setInputValue(e.target.value)}
    80.           onBlur={handleInputConfirm}
    81.           onPressEnter={handleInputConfirm}
    82.         />
    83.       )}
    84.       {!inputVisible && (
    85.         
    86.           onClick={() => setInputVisible(true)}
    87.           style={{ background: '#fff', borderStyle: 'dashed' }}
    88.         >
    89.           type="plus-circle" /> {title}
    90.         
    91.       )}
    92.     
    93.   );
    94. });
    95. TagInput.propTypes = {
    96.   title: PropTypes.string// 新增一个tag的标题
    97.   separator: PropTypes.string// 分隔符
    98.   maxLength: PropTypes.number, // tag最大长度
    99.   color: PropTypes.string// tag颜色
    100.   form: PropTypes.object, // form
    101.   key: PropTypes.string// form的key
    102. };
    103. export default TagInput;
  • 相关阅读:
    【Javascript系统学习】(二)
    【2023联发科提前批笔试题】~ 题目及参考答案
    洛谷P3807 lucas定理模板
    参数优化文档介绍
    基于python的新生入学管理系统设计与实现-计算机毕业设计源码+LW文档
    [附源码]java毕业设计社区新冠疫情防控网站
    深度学习三巨头邀你来参会!赢取RTX 3090!NVIDIA GTC 2022 AI 大会来了!
    百度SEO优化技巧与布局(提升网站排名的5种有效方法)
    数学物理方程:球函数
    29.Xaml TreeView控件---->树形控件,节点的形式显示数据
  • 原文地址:https://blog.csdn.net/weixin_37786060/article/details/132613802