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+typescript+antd+redux+less+vw自适应项目
从零开始搭建react+typescript+antd+redux+less+vw自适应项目
52 0
|
5天前
|
前端开发 JavaScript 定位技术
Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档
Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档
21 0
|
2月前
|
前端开发 JavaScript 安全
使用React、TypeScript和Ant Design构建现代化前端应用
使用React、TypeScript和Ant Design构建现代化前端应用
28 0
|
2月前
|
开发框架 前端开发 JavaScript
使用React、Redux和Bootstrap构建社交媒体应用
使用React、Redux和Bootstrap构建社交媒体应用
15 0
|
2月前
|
前端开发 JavaScript 架构师
react+typescript+umi+dva+antd
react+typescript+umi+dva+antd
28 0
|
3月前
|
存储 开发框架 前端开发
深入探索React:构建动态、交互式前端应用的终极指南
深入探索React:构建动态、交互式前端应用的终极指南
82 0
|
4月前
|
存储 前端开发 JavaScript
探索 React Hooks 的世界:如何构建出色的组件(下)
探索 React Hooks 的世界:如何构建出色的组件(下)
探索 React Hooks 的世界:如何构建出色的组件(下)
|
4月前
|
缓存 前端开发 API
探索 React Hooks 的世界:如何构建出色的组件(上)
探索 React Hooks 的世界:如何构建出色的组件(上)
探索 React Hooks 的世界:如何构建出色的组件(上)
|
4月前
|
移动开发 前端开发 JavaScript
React:构建用户界面的强大工具
React:构建用户界面的强大工具
18 0