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月前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
1月前
|
前端开发 JavaScript 开发者
使用React和Redux构建高效的前端应用
使用React和Redux构建高效的前端应用
36 1
|
1月前
|
前端开发 JavaScript 开发者
从零开始构建你的第一个React应用
从零开始构建你的第一个React应用
34 2
|
1月前
|
资源调度 前端开发 数据可视化
构建高效的数据可视化仪表板:D3.js与React的融合之道
【10月更文挑战第25天】在数据驱动的时代,将复杂的数据集转换为直观、互动式的可视化表示已成为一项至关重要的技能。本文深入探讨了如何结合D3.js的强大可视化功能和React框架的响应式特性来构建高效、动态的数据可视化仪表板。文章首先介绍了D3.js和React的基础知识,然后通过一个实际的项目案例,详细阐述了如何将两者结合使用,并提供了实用的代码示例。无论你是数据科学家、前端开发者还是可视化爱好者,这篇文章都将为你提供宝贵的洞见和实用技能。
67 5
|
1月前
|
前端开发 JavaScript Android开发
掌握React Native,构建跨平台移动应用的利器
掌握React Native,构建跨平台移动应用的利器
|
1月前
|
前端开发 JavaScript API
React Native实战:构建跨平台移动应用
React Native实战:构建跨平台移动应用
35 0
|
1月前
|
前端开发 JavaScript 网络架构
从零开始构建你的第一个React应用
从零开始构建你的第一个React应用
23 0
|
1月前
|
前端开发 JavaScript 开发者
从零构建你的第一个React应用
从零构建你的第一个React应用
23 0
|
2月前
|
前端开发 JavaScript UED
构建现代Web应用:使用React框架打造单页面应用
【10月更文挑战第9天】构建现代Web应用:使用React框架打造单页面应用
|
2月前
|
前端开发 JavaScript 测试技术
构建响应式Web应用程序:React实战指南
【10月更文挑战第9天】构建响应式Web应用程序:React实战指南