React 折騰記 - (8) 基於React+Antd封裝選擇單個文章分類(從構建到獲取)
隨著管理的文章數量增多,預設的幾個分類滿足不了現狀了...
趁著重構的過程把相關的功能考慮進去
本來想自己從頭寫過一個,看了下 Antd
有內建該型別的控制元件了,就沒必要自己造了
一般自己寫,肯定優先考慮陣列物件格式 [{tagName:'a',value:1}]
;
Antd
提供的是純陣列, [string,string]
,那如何不改變它提供的格式情況下拿到我們想要的!
拓展部分我們需要的東東,有興趣的瞧瞧,沒興趣的止步..
效果圖

需求分析及思路
需求梳理
- 從介面拿到
tags
陣列,tags
支援刪除新增 - 高亮
tag
,追加刪除的情況要考慮進去(刪除要考慮進去); - 第一個為預設分類,不允許刪除
- 標籤文字過長,則截斷,用氣泡懸浮來展示完全的文字
- 不允許新增同樣的(阻止並給予反饋)
- 預設值初始化並且回饋
- 把值丟給父
實現
- 用
dva
的effect
維護介面資料的獲取 - 子元件除了暴露返回值,不做任何涉及
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 |
追加按鈕文字替換 | 字串 |