React 16.x折腾记 - (8) 基于React+Antd封装选择单个文章分类(从构建到获取)

简介: 随着管理的文章数量增多,默认的几个分类满足不了现状了,趁着重构的过程把相关的功能考虑进去本来想自己从头写过一个,看了下Antd有内置该类型的控件了,就没必要自己造了一般自己写,肯定优先考虑数组对象格式[{tagName:'a',value:1}];Antd提供的是纯数组,[string,string],那如何不改变它提供的格式情况下拿到我们想要的!拓展部分我们需要的东东,有兴趣的瞧瞧,没兴趣的止步..


前言


随着管理的文章数量增多,默认的几个分类满足不了现状了,趁着重构的过程把相关的功能考虑进去


本来想自己从头写过一个,看了下Antd有内置该类型的控件了,就没必要自己造了

一般自己写,肯定优先考虑数组对象格式[{tagName:'a',value:1}];


Antd提供的是纯数组,[string,string],那如何不改变它提供的格式情况下拿到我们想要的!


拓展部分我们需要的东东,有兴趣的瞧瞧,没兴趣的止步..

效果图



需求分析及思路


需求梳理


  • 从接口拿到tags数组且构建枚举对象,tags支持删除添加 ,
  • 高亮tag,追加删除的情况要考虑进去;
  • 第一个为默认分类,不允许删除
  • 高亮颜色支持传入
  • 标签文字过长,则截断,用气泡悬浮来展示完全的文本
  • 不允许添加同样的(阻止并给予反馈)
  • 默认值初始化并且回馈,把值丢给父


实现


  • dvaeffect维护接口数据的获取
  • 子组件除了暴露返回值,不做任何涉及Dva这类不纯的东西,一切靠props丢进去


代码实现


在引用处的父组件构建数据获取,主要构建两个,一个待渲染的数组,一个是枚举(其实就是key-value映射);


因为要考虑和以前的版本兼容,所有一些固定的key-value,还有默认值也要考虑进去(请求失败的时候)


DocumentType.js


/*
 * @Author: CRPER
 * @LastEditors: CRPER
 * @Github: https://github.com/crper
 * @Motto: 折腾是一种乐趣,求知是一种追求。不懂就学,懂则分享。
 * @Description: 文档类型维护
 */
import React, { PureComponent } from 'react';
import { Tag, Input, Tooltip, Icon, message } from 'antd';
// 对象深比较
import isEqual from 'lodash/isEqual';
export default class DocumentType extends PureComponent {
  static getDerivedStateFromProps(nextProps, prevState) {
    if (isEqual(nextProps.data, prevState.prevData)) {
      return null;
    }
    if (nextProps.data) {
      return {
        defaultValue: nextProps.defaultValue ? nextProps.defaultValue : null,
        tags: nextProps.data,
        prevData: nextProps.data,
      };
    } else {
      return null;
    }
  }
  state = {
    tags: [], // 标签列表
    hightlightIndeX: 0, // 若是外部没有
    inputVisible: false, // 输入框默认隐藏
    inputValue: '', // 输入框默认值
  };
  //获取默认值
  initDefaultValue = () => {
    const { defaultValue, hightlightIndeX, tags } = this.state;
    // 若是有,则取遍历取得;若是外部没有传入默认值则取数组第一位
    if (defaultValue) {
      let index = tags.indexOf(defaultValue);
      // 若是传入的默认值不存在,则默认取下标为0的
      index = index === -1 ? 0 : index;
      this.setState({ hightlightIndeX: index }, () => {
        this.props.onChange(this.getTagValueFromIndex(index));
      });
    } else {
      this.props.onChange(this.getTagValueFromIndex(hightlightIndeX));
    }
  };
  componentDidMount = () => {
    this.initDefaultValue();
  };
  // 记录控件的ref
  input = React.createRef();
  // 显示input后,直接聚焦
  showInput = () => {
    this.setState({ inputVisible: true }, () => this.input.current.focus());
  };
  // 保存input输入的值
  handleInputChange = e => {
    this.setState({ inputValue: e.target.value });
  };
  // 新增判定
  handleInputConfirm = () => {
    const { inputValue, tags: prevTags, defaultValue } = this.state;
    // 若是输入的值已经存在或空值,则不添加
    if (inputValue === defaultValue) {
      message.error('已存在同样的类型!!!');
      this.setState({ inputValue: '' });
      this.input.focus();
      return false;
    }
    if (!inputValue) {
      this.setState({ inputVisible: false, inputValue: '' });
      return false;
    }
    let tags = prevTags;
    if (inputValue && tags.indexOf(inputValue) === -1) {
      tags = [...tags, inputValue];
    }
    this.setState({
      tags,
      inputVisible: false,
      inputValue: '',
    });
    // 传递给父的新增标签回调
    if (this.props.addTag) {
      this.props.addTag(inputValue);
    }
  };
  // 取得对应index下的tag的值
  getTagValueFromIndex = index => {
    const { tags } = this.state;
    return tags[index];
  };
  // 高亮TAG
  hightlightTag = index => {
    this.setState({ hightlightIndeX: index });
    if (this.props.onChange) {
      this.props.onChange(this.getTagValueFromIndex(index));
    }
  };
  // 删除tag
  handleClose = removeTag => {
    const { hightlightIndeX, tags } = this.state;
    if (this.props.removeTag) {
      this.props.removeTag(removeTag);
    }
    // 若是删除的位置和高亮的位置同一个,则高亮往前一位
    if (tags.indexOf(removeTag) === tags.length - 1) {
      this.hightlightTag(hightlightIndeX - 1);
    }
  };
  render() {
    const { tags, inputVisible, inputValue, hightlightIndeX } = this.state;
    const { plusBtnText, activeColor } = this.props;
    return (
      <div>
        {tags.map((tag, index) => {
          const isLongTag = tag.length > 10;
          const tagElem = (
            <Tag
              key={tag}
              color={hightlightIndeX === index ? (activeColor ? activeColor : '#40a9ff') : ''}
              closable={index !== 0}
              onClick={() => this.hightlightTag(index)}
              afterClose={() => this.handleClose(tag)}
            >
              {isLongTag ? `${tag.slice(0, 10)}...` : tag}
            </Tag>
          );
          return isLongTag ? (
            <Tooltip title={tag} key={tag}>
              {tagElem}
            </Tooltip>
          ) : (
            tagElem
          );
        })}
        {inputVisible && (
          <Input
            ref={this.input}
            type="text"
            size="small"
            style={{ width: 78 }}
            value={inputValue}
            onChange={this.handleInputChange}
            onBlur={this.handleInputConfirm}
            onPressEnter={this.handleInputConfirm}
          />
        )}
        {!inputVisible && (
          <Tag onClick={this.showInput} style={{ background: '#fff', borderStyle: 'dashed' }}>
            <Icon type="plus" /> {plusBtnText ? plusBtnText : 'New Tag'}
          </Tag>
        )}
      </div>
    );
  }
}


用法


写成受控组件,无数据不渲染


props 解释 格式类型 是否可选
data 待遍历的数组 数组 必选
onChange 选中的回调 函数 必选
addTag 添加标签的回调 函数 必选
remvoeTag 移除标签的回调 函数 必选
defaultValue 默认值 字符串 可选
plusBtnText 追加按钮文本替换 字符串 可选
activeColor 高亮的颜色 字符串 可选


{typeNames && typeNames.length > 0 ? (
          <Row type="flex" justify="start" align="middle">
            <span style={{ fontSize: 16, fontWeight: 700 }}>文章类型</span>
            <Divider type="vertical" />
            <DocumentType
              data={typeNames}
              onChange={this.getTagValue}
              addTag={this.addTag}
              removeTag={this.removeTag}
              defaultValue="草稿"
              activeColor="#108ee9"
              plusBtnText="新的分类"
            />
          </Row>
        ) : null}


目录
相关文章
|
2月前
|
前端开发 JavaScript 网络架构
react对antd中Select组件二次封装
本文介绍了如何在React中对Ant Design(antd)的Select组件进行二次封装,包括创建MSelect组件、定义默认属性、渲染Select组件,并展示了如何使用Less进行样式定义和如何在项目中使用封装后的Select组件。
91 2
react对antd中Select组件二次封装
|
2月前
|
前端开发
React给antd中TreeSelect组件左侧加自定义图标icon
本文介绍了如何在React中为Ant Design的TreeSelect组件的每个树节点添加自定义图标,并解决了因缺少key属性而导致的警告问题,展示了如何通过递归函数处理treeData数据并为每个节点添加图标。
115 2
React给antd中TreeSelect组件左侧加自定义图标icon
|
2月前
|
前端开发
react使用antd中的Checkbox实现多选
在React项目中,通过Ant Design的Checkbox组件实现多选。引入Checkbox,使用Checkbox.Group来管理Checkbox,设置`value`属性绑定选中项数组,通过`onChange`更新数组。维护一个全选状态,根据选中项数量与总数决定全选按钮状态。全选按钮的`onChange`事件用于控制所有Checkbox的选中状态。
88 1
react使用antd中的Checkbox实现多选
|
2月前
|
前端开发
React添加路径别名alias、接受props默认值、并二次封装antd中Modal组件与使用
本文介绍了在React项目中如何添加路径别名alias以简化模块引入路径,设置组件props的默认值,以及如何二次封装Ant Design的Modal组件。文章还提供了具体的代码示例,包括配置Webpack的alias、设置defaultProps以及封装Modal组件的步骤和方法。
68 1
React添加路径别名alias、接受props默认值、并二次封装antd中Modal组件与使用
|
1月前
|
前端开发
react 封装防抖
react 封装防抖
32 4
|
2月前
|
前端开发 JavaScript 区块链
react18函数组件+antd使用指南-使用代码集合以及报错记录汇总
本文介绍了多个React开发中常见的问题及其解决方案,包括但不限于:1)`useForm`实例未连接到任何`Form`元素的警告及解决方法;2)监听页面滚动事件的实现方式;3)React 18与antd 5.8.6中定制主题的方法;4)React结合antd 4.x版本自定义主题色的步骤;5)解决`ResizeObserver loop`相关报错的技巧;6)处理React设计表单时遇到的CDN资源加载失败问题;7)解决onClick事件传参问题;8)修复类型错误等。每部分均提供详细分析与实用代码示例,帮助开发者快速定位并解决问题。
46 2
|
2月前
|
前端开发 数据安全/隐私保护
react antd 实现修改密码(原密码,新密码,再次输入新密码,新密码增加正则复杂度校验)
文章介绍了如何在React项目中使用Ant Design实现一个修改密码的组件,包括原密码、新密码和再次输入新密码的表单项,并为新密码增加了正则表达式复杂度校验。
73 0
react antd 实现修改密码(原密码,新密码,再次输入新密码,新密码增加正则复杂度校验)
|
2月前
|
前端开发 计算机视觉
React使用antd实现可编辑单元格
React结合Ant Design实现可编辑单元格的表格组件,通过EditableRow和EditableCell封装实现单元格编辑功能,并提供saveFun回调保存编辑内容。
94 1
|
2月前
|
前端开发
React按需加载antd步骤以及出现的问题
在使用`babel-plugin-import`插件时,可以在项目的根目录创建`.babelrc`文件或在`package.json`中添加babel配置。这两个文件中不应该存在重复的配置。如果出现"Multiple configuration files found"错误,需要选择其中一个文件进行配置,并删除另一个文件中的babel配置。使用该插件后,可以直接从`antd`引入组件,无需手动引入样式文件。
63 1
|
2月前
封装react-antd-table组件参数以及方法如rowSelection、pageNum、pageSize、分页方法等等
文章介绍了如何封装React-Antd的Table组件,包括参数和方法,如行选择(rowSelection)、页码(pageNum)、页面大小(pageSize)、分页方法等,以简化在不同表格组件中的重复代码。
61 0