专栏首页CRPER折腾记React 折腾记 - (8) 基于React+Antd封装选择单个文章分类(从构建到获取)

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

前言

随着管理的文章数量增多,默认的几个分类满足不了现状了...

趁着重构的过程把相关的功能考虑进去

本来想自己从头写过一个,看了下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();
  };

  // 显示input后,直接聚焦
  showInput = () => {
    this.setState({ inputVisible: true }, () => this.input.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);
    }
  };

  // 记录控件的ref
  saveInputRef = input => (this.input = input);

  render() {
    const { tags, inputVisible, inputValue, hightlightIndeX } = this.state;
    const { plusBtnText } = this.props;
    return (
      <div>
        {tags.map((tag, index) => {
          const isLongTag = tag.length > 10;
          const tagElem = (
            <Tag
              key={tag}
              closable={index !== 0}
              style={hightlightIndeX === index ? { color: '#fff', background: '#108ee9' } : {}}
              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.saveInputRef}
            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>
    );
  }
}

复制代码

用法

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

{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="草稿"
              plusBtnText="新的分类"
            />
          </Row>
        ) : null}

复制代码

props

解释

格式类型

data

待遍历的数组

数组

onChange

选中的回调

函数

addTag

添加标签的回调

函数

remvoeTag

移除标签的回调

函数

defaultValue

默认值

字符串

plusBtnText

追加按钮文本替换

字符串


总结

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue 折腾记 - (13) Nuxt.js写一个常规音频的播放组件,动态注入微信,新浪微博的js-sdk

    全部耦合到组件内,虽然可以正常播放(包括微信和微博) 且不是单例模式,对于多音频页面,有毒

    CRPER
  • Vue 折腾记 - (8) 写一个挺靠谱的多地区选择组件

    本来想写个props映射下regionName,regionId,child; 但是感觉作用不大,就没写了,(一般公司的地区JSON格式定下来了之后变动的可能性...

    CRPER
  • React 折腾记 - (3) 结合Mobx实现一个比较靠谱的动态tab水平菜单,同时关联侧边栏

    CRPER
  • 实现红警式的建筑物拖拽生成特效

    望月从良
  • Vue双向绑定原理,教你一步一步实现双向绑定

    当今前端天下以 Angular、React、vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋。

    六小登登
  • KUOKUO的趣味教程 | 小怪物的视野(2)

    本篇承接上一集故事《KUOKUO的趣味教程 | 进击的小怪诞生(1)》,看小怪是如何自我进化的!

    张晓衡
  • vue+element的表格分页和前端搜索

    1.前端后台管理会存在很多表格,表格数据过多就需要分页; 2.前端交互每次搜索如果都请求服务器会加大服务器的压力,所以在数据量不是很大的情况下可以一次性将数据返...

    火狼1
  • 通过vue.js 学习来总结es6语法中的箭头函数,箭头函数原理分析。

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/acoolgiser/article/details/...

    acoolgiser
  • 使用物理引擎Box2D设计类愤怒小鸟的击球游戏--基本架构设置

    望月从良
  • Vue 绑定简单分析实现

    使用js es6 中 Object.defineProperty为我们自己定义的VM创建示例。同时这个方法通过提供了set.get方法的触发我们的监听事件。

    deep_sadness

扫码关注云+社区

领取腾讯云代金券