React Ant Design樹形表格的復(fù)雜增刪改操作
最近因為業(yè)務(wù)接觸了antd,使用antd完成一個復(fù)雜的樹形表格的顯示以及修改。在這其中遇見了不少坑,很多功能antd只寫了初步的功能,更為細(xì)化的功能只能自己完善。踩過的坑都寫在了這里。
樹形表格的顯示
在antd中對于表格的key值有著嚴(yán)格的控制,每一個row都必須有一個獨一無二的key值,可以是數(shù)字也可以是字符串。這一點和我曾經(jīng)使用過得iview有著很大的區(qū)別。react使用key來代表每一行是為了避免重新渲染的問題,這個優(yōu)化也在實際的開發(fā)中帶來了不少的問題。比如新建行時需要自定義新key。
下面直接上一下代碼及代碼效果,這是一個三級的樹形表格,且其中包含二級標(biāo)題。
最終效果

colums標(biāo)題: 簡易版標(biāo)題,隨著功能的增加,我們將增加colums的復(fù)雜度。
let columns = [
{
title: '題目',
dataIndex: 'text'
},
{
title: '類型',
children: [
{
title: '一級',
dataIndex: 'text1'
},
{
title: '二級',
dataIndex: 'text2',
}]
},
{
title: '內(nèi)容',
dataIndex: 'content'
},
{
title: '答案',
dataIndex: 'answer',
},
{
title: '類型',
dataIndex: 'mark_type',
className: 'line'
},
{
title: '版本',
dataIndex: 'version',
className: 'line'
},
{
title: '一級內(nèi)容點',
dataIndex: 'value1',
className: 'line'
},
{
title: '二級內(nèi)容點',
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": "語文一",
"value1": "語文",
"children": [{
"key": 121,
"value2": "選擇",
"text2": "選擇題",
"content": "題目內(nèi)容",
"answer": "A",
"mark_type": "1",
"version": "1"
},{
"key": 122,
"value2":"填空",
"text2": "填空題",
"content": "題目內(nèi)容",
"answer": "梅花",
"mark_type": "1",
"version": "1"
},{
"key": 123,
"value2": "閱讀",
"text2": "閱讀題",
"content": "題目內(nèi)容",
"answer": "野蠻生長",
"mark_type": "1",
"version": "1"
},{
"key": 124,
"value2": "文言文",
"text2": "文言文",
"content": "題目內(nèi)容",
"answer": "滕王閣序",
"mark_type": "1",
"version": "1"
}],
}],
}, {
"key": 2,
"text": "題目二",
"children": [ {
"key": 21,
"text1": "英語一",
"value1": "英語",
"children": [{
"key": 211,
"value2": "完型",
"text2": "完形填空",
"content": "題目內(nèi)容",
"answer": "ABC",
"mark_type": "2",
"version": "1"
},{
"key": 212,
"value2": "一級代碼",
"text2": "選擇",
"content": "題目內(nèi)容",
"answer": "D",
"mark_type": "2",
"version": "1"
}],
}],
}];
增加子項數(shù)據(jù)
增加子項數(shù)據(jù)使用操作中的增加按鈕進(jìn)行增加,增加按鈕設(shè)置為圖形,更為形象具體清晰化。
//button樣式使用antd自帶icon樣式
<Button type="primary" shape="circle" icon="plus" size={'small'}
onClick={this.handleAdd.bind(this, record)}
/>
注意事項:
1、對于最子項來說沒有增加子項選擇,需要對不同數(shù)據(jù)行進(jìn)行不同處理
2、不同數(shù)據(jù)行新增數(shù)據(jù)的內(nèi)容不同,字段也不同
3、新增之后需要點擊確認(rèn)或者取消
4、加入數(shù)據(jù)點擊確認(rèn)之后需要添加于當(dāng)前key下的children中
本想使用antd自帶的editable屬性,但是這個屬性不支持二級標(biāo)題的編輯,所以只有自己寫render
這個問題,在7102年就提出來了,但是已經(jīng)8102年了快9102年都沒有更新。
新增一級類型題目樣式

新增二級類型題目樣式

這里就部分render代碼:
//分級標(biāo)題
{
title: '類型',
children:
[{
title: '一級',
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: '二級',
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'>無效</Option>
</Select>
}
return text
}
}
注意展開
如果對一行未展開的行下新增數(shù)據(jù),那么無法看到打開的編輯狀態(tài)是一個非常糟糕的問題。所以我們需要在新增子項的時候自動展開父項。
//steate初始化
expandedRows: [] //展開行
//render 初始化
<Table
bordered
expandedRowKeys={this.state.expandedRows}
onExpandedRowsChange={this.changeExpandedRows.bind(this)}
/>
//自動展開變化,獲取當(dāng)前展開行
changeExpandedRows = (expandedRows) => {
this.setState({
expandedRows
})
};
//增加函數(shù)中
let rows = this.state.expandedRows;
rows.push(record.key);
this.setState({
expandedRows: rows
});
刪除行數(shù)據(jù)
刪除在antd中相較比較簡單,因為antd的table每行都有key屬性,key是唯一且必須的屬性,所以只要過濾掉包含目的key的數(shù)據(jù)即可視為刪除。因為這是樹形數(shù)據(jù),普通的遍歷無法進(jìn)行操作,所以使用深度優(yōu)先遍歷來進(jìn)行數(shù)據(jù)處理,也可以使用廣度優(yōu)先,根據(jù)數(shù)據(jù)來變更。
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相同,不過修改需要保存之前的值。需要在input或select中設(shè)置defaultValue={text}來保證初始值相同
保存
無論是新增修改還是刪除,都需要進(jì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ù)組中的順序,使用簡單的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)行變量的保存,這里就沒有多寫了。總的來說antd是個好組件,大部分功能都很齊全,但是很多細(xì)節(jié)還是需要自己進(jìn)行完善。
補充知識:Antd(Ant-design),嵌套子表格(expandedRowRender)的異步獲取數(shù)據(jù)
使用阿里的ant-design開源框架,要在表格里面嵌套子表格,需要在用戶點擊父表格的一行數(shù)據(jù)后,獲取該行的key,然后去異步請求后臺的數(shù)據(jù)用來填充子表格的內(nèi)容。
如果這樣寫(省略無關(guān)代碼):
expandedRowRender = (record) => {
dispatch({
type: 'flow/getPlanList',
payload: {
contractId: record.contract_id, // 該參數(shù)是從父表格帶過來的key
},
callback: () => {
const {
flow: { data },
} = this.props;
this.setState({
secData: data.list,
});
console.log("返回數(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>
)
}
則會出現(xiàn)不斷的發(fā)起請求的現(xiàn)象:

這是因為,expandedRowRender 實際上是在 Table 組件的 render 方法中調(diào)用的,React render 中用 dispatch 會造成重復(fù)調(diào)用的問題,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("返回數(shù)據(jù)(PlanList):" + JSON.stringify(this.state.secData));
}
});
}
但是單純的這樣做,又會帶來新的問題,就是子表格的所有數(shù)據(jù)都變成了相同的。
本人的解決辦法是使用鍵值對。
onExpand = (expanded, record) => {
const { dispatch } = this.props;
if (expanded === false) {
// 因為如果不斷的添加鍵值對,會造成數(shù)據(jù)過于龐大,浪費資源,
// 因此在每次合并的時候講相應(yīng)鍵值下的數(shù)據(jù)清空
console.log("合并!");
this.setState({
subTabData: {
...this.state.subTabData,
[record.contract_id]: [] ,
}
});
} else {
console.log("展開!");
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("返回數(shù)據(jù)(PlanList):" + JSON.stringify(this.state.subTabData));
}
});
}
}
以上這篇React Ant Design樹形表格的復(fù)雜增刪改操作就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解React中錯誤邊界的原理實現(xiàn)與應(yīng)用
在React中,錯誤邊界是一種特殊的組件,用于捕獲其子組件樹中發(fā)生的JavaScript錯誤,并防止這些錯誤冒泡至更高層,導(dǎo)致整個應(yīng)用崩潰,下面我們就來看看它的具體應(yīng)用吧2024-03-03
React-Native TextInput組件詳解及實例代碼
這篇文章主要介紹了React-Native TextInput組件詳解及實例代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10
react在安卓中輸入框被手機(jī)鍵盤遮擋問題的解決方法
這篇文章主要給大家介紹了關(guān)于react在安卓中輸入框被手機(jī)鍵盤遮擋問題的解決方法,文中通過圖文以及示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧2018-09-09

