前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 16.x折腾记 - (8) 基于React+Antd封装选择单个文章分类(从构建到获取)

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

作者头像
CRPER
发布2024-02-01 08:59:32
1240
发布2024-02-01 08:59:32
举报
文章被收录于专栏:CRPER折腾记

前言

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

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

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

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

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

效果图

需求分析及思路

需求梳理

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

实现

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

代码实现

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

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

DocumentType.js

代码语言:javascript
复制
/*
 * @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

高亮的颜色

字符串

可选

代码语言:javascript
复制
{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}

总结

不对之处请留言,会及时修正.谢谢阅读.

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-02-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 效果图
  • 需求分析及思路
  • 代码实现
    • DocumentType.js
    • 用法
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档