React Ant Design樹(shù)形表格的復(fù)雜增刪改操作
最近因?yàn)闃I(yè)務(wù)接觸了antd,使用antd完成一個(gè)復(fù)雜的樹(shù)形表格的顯示以及修改。在這其中遇見(jiàn)了不少坑,很多功能antd只寫(xiě)了初步的功能,更為細(xì)化的功能只能自己完善。踩過(guò)的坑都寫(xiě)在了這里。
樹(shù)形表格的顯示
在antd中對(duì)于表格的key值有著嚴(yán)格的控制,每一個(gè)row都必須有一個(gè)獨(dú)一無(wú)二的key值,可以是數(shù)字也可以是字符串。這一點(diǎn)和我曾經(jīng)使用過(guò)得iview有著很大的區(qū)別。react使用key來(lái)代表每一行是為了避免重新渲染的問(wèn)題,這個(gè)優(yōu)化也在實(shí)際的開(kāi)發(fā)中帶來(lái)了不少的問(wèn)題。比如新建行時(shí)需要自定義新key。
下面直接上一下代碼及代碼效果,這是一個(gè)三級(jí)的樹(shù)形表格,且其中包含二級(jí)標(biāo)題。
最終效果
colums標(biāo)題: 簡(jiǎn)易版標(biāo)題,隨著功能的增加,我們將增加colums的復(fù)雜度。
let columns = [ { title: '題目', dataIndex: 'text' }, { title: '類(lèi)型', children: [ { title: '一級(jí)', dataIndex: 'text1' }, { title: '二級(jí)', dataIndex: 'text2', }] }, { title: '內(nèi)容', dataIndex: 'content' }, { title: '答案', dataIndex: 'answer', }, { title: '類(lèi)型', dataIndex: 'mark_type', className: 'line' }, { title: '版本', dataIndex: 'version', className: 'line' }, { title: '一級(jí)內(nèi)容點(diǎn)', dataIndex: 'value1', className: 'line' }, { title: '二級(jí)內(nèi)容點(diǎn)', dataIndex: 'value2', className: 'line' }, { title: '操作', key: 'action', width: 205 } ];
data數(shù)據(jù):
let data = [{ "key": 1, "text": "題目一", "children": [{ "key": 11, "text1": "數(shù)學(xué)一", "children":[] }, { "key": 12, "text1": "語(yǔ)文一", "value1": "語(yǔ)文", "children": [{ "key": 121, "value2": "選擇", "text2": "選擇題", "content": "題目?jī)?nèi)容", "answer": "A", "mark_type": "1", "version": "1" },{ "key": 122, "value2":"填空", "text2": "填空題", "content": "題目?jī)?nèi)容", "answer": "梅花", "mark_type": "1", "version": "1" },{ "key": 123, "value2": "閱讀", "text2": "閱讀題", "content": "題目?jī)?nèi)容", "answer": "野蠻生長(zhǎng)", "mark_type": "1", "version": "1" },{ "key": 124, "value2": "文言文", "text2": "文言文", "content": "題目?jī)?nèi)容", "answer": "滕王閣序", "mark_type": "1", "version": "1" }], }], }, { "key": 2, "text": "題目二", "children": [ { "key": 21, "text1": "英語(yǔ)一", "value1": "英語(yǔ)", "children": [{ "key": 211, "value2": "完型", "text2": "完形填空", "content": "題目?jī)?nèi)容", "answer": "ABC", "mark_type": "2", "version": "1" },{ "key": 212, "value2": "一級(jí)代碼", "text2": "選擇", "content": "題目?jī)?nèi)容", "answer": "D", "mark_type": "2", "version": "1" }], }], }];
增加子項(xiàng)數(shù)據(jù)
增加子項(xiàng)數(shù)據(jù)使用操作中的增加按鈕進(jìn)行增加,增加按鈕設(shè)置為圖形,更為形象具體清晰化。
//button樣式使用antd自帶icon樣式 <Button type="primary" shape="circle" icon="plus" size={'small'} onClick={this.handleAdd.bind(this, record)} />
注意事項(xiàng):
1、對(duì)于最子項(xiàng)來(lái)說(shuō)沒(méi)有增加子項(xiàng)選擇,需要對(duì)不同數(shù)據(jù)行進(jìn)行不同處理
2、不同數(shù)據(jù)行新增數(shù)據(jù)的內(nèi)容不同,字段也不同
3、新增之后需要點(diǎn)擊確認(rèn)或者取消
4、加入數(shù)據(jù)點(diǎn)擊確認(rèn)之后需要添加于當(dāng)前key下的children中
本想使用antd自帶的editable屬性,但是這個(gè)屬性不支持二級(jí)標(biāo)題的編輯,所以只有自己寫(xiě)render
這個(gè)問(wèn)題,在7102年就提出來(lái)了,但是已經(jīng)8102年了快9102年都沒(méi)有更新。
新增一級(jí)類(lèi)型題目樣式
新增二級(jí)類(lèi)型題目樣式
這里就部分render代碼:
//分級(jí)標(biāo)題 { title: '類(lèi)型', children: [{ title: '一級(jí)', dataIndex: 'text1', render: (text, record) => { if (this.state.isEditing && this.state.editingOneKey === record.key) return <Input defaultValue={text} onChange={(e) => { this.changeEdit(e, record.key, 'text1') }} /> return text } }, { title: '二級(jí)', dataIndex: 'text2', render: (text, record) => { if (this.state.isEditing && this.state.editingTwoKey === record.key) return <Input defaultValue={text} onChange={(e) => { this.changeEdit(e, record.key, 'text2') }} /> return text } }] } //select選擇 { title: '內(nèi)容', dataIndex:'content', render:(text, record) => { if (text === 0) text = '內(nèi)容一'; if (text === 1) text = '內(nèi)容二'; if (this.state.isEditing && this.state.editingTwoKey === record.key) { return <Select defaultValue={text} onChange={(e) => { this.changeEdit(e, record.key, 'flag') }} getPopupContainer={triggerNode => triggerNode.parentNode}> <Option value='0'>有效</Option> <Option value='1'>無(wú)效</Option> </Select> } return text } }
注意展開(kāi)
如果對(duì)一行未展開(kāi)的行下新增數(shù)據(jù),那么無(wú)法看到打開(kāi)的編輯狀態(tài)是一個(gè)非常糟糕的問(wèn)題。所以我們需要在新增子項(xiàng)的時(shí)候自動(dòng)展開(kāi)父項(xiàng)。
//steate初始化 expandedRows: [] //展開(kāi)行 //render 初始化 <Table bordered expandedRowKeys={this.state.expandedRows} onExpandedRowsChange={this.changeExpandedRows.bind(this)} /> //自動(dòng)展開(kāi)變化,獲取當(dāng)前展開(kāi)行 changeExpandedRows = (expandedRows) => { this.setState({ expandedRows }) }; //增加函數(shù)中 let rows = this.state.expandedRows; rows.push(record.key); this.setState({ expandedRows: rows });
刪除行數(shù)據(jù)
刪除在antd中相較比較簡(jiǎn)單,因?yàn)閍ntd的table每行都有key屬性,key是唯一且必須的屬性,所以只要過(guò)濾掉包含目的key的數(shù)據(jù)即可視為刪除。因?yàn)檫@是樹(shù)形數(shù)據(jù),普通的遍歷無(wú)法進(jìn)行操作,所以使用深度優(yōu)先遍歷來(lái)進(jìn)行數(shù)據(jù)處理,也可以使用廣度優(yōu)先,根據(jù)數(shù)據(jù)來(lái)變更。
handleDelete = (key) => { let data = this.state.data; data = dsFilter(data, key); this.setState({ data }); function dsFilter(dealData, dealKey) { for (let i = 0; i < dealData.length; i++) { if (dealData[i].children && dealData[i].children.length > 0) { dealData[i].children = dsFilter(dealData[i].children, dealKey); } } return dealData.filter(item => item.key !== dealKey); } };
修改某些字段
修改和新增的colums相同,不過(guò)修改需要保存之前的值。需要在input或select中設(shè)置defaultValue={text}來(lái)保證初始值相同
保存
無(wú)論是新增修改還是刪除,都需要進(jìn)行保存。這里使用✓和✗ 來(lái)代表確定和取消。
同時(shí)需要注意,如果在一行的編輯中點(diǎn)擊其他行的操作,需要將之前行的操作視為取消。
<div> <Button shape="circle" icon="check" size={'small'} style={{ backgroundColor: '#65BF34', color: '#FFF', border: 'none' }} onClick={this.saveEdit.bind(this, record.key)} /> <Button shape="circle" icon="close" size={'small'} style={{ backgroundColor: '#FF3333', color: '#FFF', border: 'none' }} onClick={this.cancelEdit.bind(this, record.key)} /> </div>
修改順序
修改順序就是修改數(shù)據(jù)在數(shù)組中的順序,使用簡(jiǎn)單的temp進(jìn)行交換即可。
shiftUp = (key) => { let data = this.state.data; data = dsShift(data, key); this.setState({ data }); function dsShift(dealData, dealKey) { for (let i = 0; i < dealData.length; i++) { if (dealData[i].children && dealData[i].children.length > 0) { dealData[i].children = dsShift(dealData[i].children, dealKey); } if (dealData[i].key === dealKey) { if (i === 0) { message.warning('該行已置頂!'); break; } let temp = dealData[i - 1]; dealData[i - 1] = dealData[i]; dealData[i] = temp; break; } } return dealData; } }; shiftDown = (key) => { let data = this.state.data; data = dsShift(data, key); this.setState({ data }); function dsShift(dealData, dealKey) { for (let i = 0; i < dealData.length; i++) { if (dealData[i].children && dealData[i].children.length > 0) { dealData[i].children = dsShift(dealData[i].children, dealKey); } if (dealData[i].key === dealKey) { if (i === dealData.length - 1) { message.warning('該行已置尾!'); break; } let temp = dealData[i + 1]; dealData[i + 1] = dealData[i]; dealData[i] = temp; break; } } return dealData; } };
總結(jié)
在進(jìn)行表格數(shù)據(jù)操作中,需要進(jìn)行變量的保存,這里就沒(méi)有多寫(xiě)了??偟膩?lái)說(shuō)antd是個(gè)好組件,大部分功能都很齊全,但是很多細(xì)節(jié)還是需要自己進(jìn)行完善。
補(bǔ)充知識(shí):Antd(Ant-design),嵌套子表格(expandedRowRender)的異步獲取數(shù)據(jù)
使用阿里的ant-design開(kāi)源框架,要在表格里面嵌套子表格,需要在用戶(hù)點(diǎn)擊父表格的一行數(shù)據(jù)后,獲取該行的key,然后去異步請(qǐng)求后臺(tái)的數(shù)據(jù)用來(lái)填充子表格的內(nèi)容。
如果這樣寫(xiě)(省略無(wú)關(guān)代碼):
expandedRowRender = (record) => { dispatch({ type: 'flow/getPlanList', payload: { contractId: record.contract_id, // 該參數(shù)是從父表格帶過(guò)來(lái)的key }, callback: () => { const { flow: { data }, } = this.props; this.setState({ secData: data.list, }); console.log("返回?cái)?shù)據(jù)(PlanList):" + JSON.stringify(this.state.secData)); } }); return ( <Table columns={secColumns} dataSource={this.state.secData} pagination={false} /> ); }; render() { return( <Card> {this.renderForm()} <div> <Table expandedRowRender={this.expandedRowRender} loading={loading} rowSelection={rowSelection} dataSource={list} columns={columns} pagination={paginationProps} scroll={{ x: 2500}} size = 'middle' expandRowByClick={true} onSelect={this.seFn} /> </div> </Card> ) }
則會(huì)出現(xiàn)不斷的發(fā)起請(qǐng)求的現(xiàn)象:
這是因?yàn)?,expandedRowRender 實(shí)際上是在 Table 組件的 render 方法中調(diào)用的,React render 中用 dispatch 會(huì)造成重復(fù)調(diào)用的問(wèn)題,dispatch -> setState -> render -> dispatch -> setState -> render,循環(huán)往復(fù)。所以應(yīng)該把 dispatch 放在 onExpand 中。
onExpand = (expanded, record) => { const { dispatch } = this.props; dispatch({ type: 'flow/getPlanList', payload: { contractId: record.contract_id, }, callback: () => { const { flow: { data }, } = this.props; this.setState({ secData: data.list , }); console.log("返回?cái)?shù)據(jù)(PlanList):" + JSON.stringify(this.state.secData)); } }); }
但是單純的這樣做,又會(huì)帶來(lái)新的問(wèn)題,就是子表格的所有數(shù)據(jù)都變成了相同的。
本人的解決辦法是使用鍵值對(duì)。
onExpand = (expanded, record) => { const { dispatch } = this.props; if (expanded === false) { // 因?yàn)槿绻粩嗟奶砑渔I值對(duì),會(huì)造成數(shù)據(jù)過(guò)于龐大,浪費(fèi)資源, // 因此在每次合并的時(shí)候講相應(yīng)鍵值下的數(shù)據(jù)清空 console.log("合并!"); this.setState({ subTabData: { ...this.state.subTabData, [record.contract_id]: [] , } }); } else { console.log("展開(kāi)!"); dispatch({ type: 'flow/getPlanList', payload: { contractId: record.contract_id, }, callback: () => { const { flow: { data }, } = this.props; this.setState({ subTabData: { ...this.state.subTabData, [record.contract_id]: data.list , } }); console.log("返回?cái)?shù)據(jù)(PlanList):" + JSON.stringify(this.state.subTabData)); } }); } }
以上這篇React Ant Design樹(shù)形表格的復(fù)雜增刪改操作就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解React中錯(cuò)誤邊界的原理實(shí)現(xiàn)與應(yīng)用
在React中,錯(cuò)誤邊界是一種特殊的組件,用于捕獲其子組件樹(shù)中發(fā)生的JavaScript錯(cuò)誤,并防止這些錯(cuò)誤冒泡至更高層,導(dǎo)致整個(gè)應(yīng)用崩潰,下面我們就來(lái)看看它的具體應(yīng)用吧2024-03-03React-Native TextInput組件詳解及實(shí)例代碼
這篇文章主要介紹了React-Native TextInput組件詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10React forwardRef的使用方法及注意點(diǎn)
React.forwardRef的API中ref必須指向dom元素而不是React組件,通過(guò)一段示例代碼給大家介紹了React forwardRef使用方法及注意點(diǎn)還有一些特殊情況分析,感興趣的朋友跟隨小編一起看看吧2021-06-06useState?解決文本框無(wú)法輸入的問(wèn)題詳解
這篇文章主要為大家介紹了useState?解決文本框無(wú)法輸入的問(wèn)題詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03react在安卓中輸入框被手機(jī)鍵盤(pán)遮擋問(wèn)題的解決方法
這篇文章主要給大家介紹了關(guān)于react在安卓中輸入框被手機(jī)鍵盤(pán)遮擋問(wèn)題的解決方法,文中通過(guò)圖文以及示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧2018-09-09React錯(cuò)誤邊界Error Boundaries
錯(cuò)誤邊界是一種React組件,這種組件可以捕獲發(fā)生在其子組件樹(shù)任何位置的JavaScript錯(cuò)誤,并打印這些錯(cuò)誤,同時(shí)展示降級(jí)UI,而并不會(huì)渲染那些發(fā)生崩潰的子組件樹(shù)2023-01-01