React 折騰記 - (8) 基於React+Antd封裝選擇單個文章分類(從構建到獲取)

CRPER發表於2018-11-26

前言

隨著管理的文章數量增多,預設的幾個分類滿足不了現狀了...

趁著重構的過程把相關的功能考慮進去

本來想自己從頭寫過一個,看了下Antd有內建該型別的控制元件了,就沒必要自己造了

一般自己寫,肯定優先考慮陣列物件格式[{tagName:'a',value:1}];

Antd提供的是純陣列,[string,string],那如何不改變它提供的格式情況下拿到我們想要的!

擴充部分我們需要的東東,有興趣的瞧瞧,沒興趣的止步..


效果圖

React 折騰記 - (8) 基於React+Antd封裝選擇單個文章分類(從構建到獲取)


需求分析及思路

需求梳理

  • 從介面拿到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}

複製程式碼

總結

不對之處請留言,會及時修正.謝謝閱讀.

相關文章