antd可編輯單元格實現原理
阿新 • • 發佈:2022-03-17
最近在專案覆盤的時候,發現使用了antd可編輯單元格。用的時候沒仔細看,現在詳細看下實現原理。
antd可編輯單元格:https://ant.design/components/table-cn/#components-table-demo-edit-cell
專案覆盤:https://www.cnblogs.com/shixiu/p/16011009.html
js程式碼
import React, { useContext, useState, useEffect, useRef } from 'react'; import { Table, Input, Button, Popconfirm, Form } from 'antd'; const EditableContext = React.createContext(null); const EditableRow = ({ index, ...props }) => { const [form] = Form.useForm(); return ( <Form form={form} component={false}> <EditableContext.Provider value={form}> <tr {...props} /> </EditableContext.Provider> </Form> ); }; const EditableCell = ({ title, editable, children, dataIndex, record, handleSave, ...restProps }) => { const [editing, setEditing] = useState(false); const inputRef = useRef(null); const form = useContext(EditableContext); useEffect(() => { if (editing) { inputRef.current.focus(); } }, [editing]); const toggleEdit = () => { setEditing(!editing); form.setFieldsValue({ [dataIndex]: record[dataIndex], }); }; const save = async () => { try { const values = await form.validateFields(); toggleEdit(); handleSave({ ...record, ...values }); } catch (errInfo) { console.log('Save failed:', errInfo); } }; let childNode = children; if (editable) { childNode = editing ? ( <Form.Item style={{ margin: 0, }} name={dataIndex} rules={[ { required: true, message: `${title} is required.`, }, ]} > <Input ref={inputRef} onPressEnter={save} onBlur={save} /> </Form.Item> ) : ( <div className="editable-cell-value-wrap" style={{ paddingRight: 24, }} onClick={toggleEdit} > {children} </div> ); } return <td {...restProps}>{childNode}</td>; }; class EditableTable extends React.Component { constructor(props) { super(props); this.columns = [ { title: 'name', dataIndex: 'name', width: '30%', editable: true, }, { title: 'age', dataIndex: 'age', }, { title: 'address', dataIndex: 'address', }, { title: 'operation', dataIndex: 'operation', render: (_, record) => this.state.dataSource.length >= 1 ? ( <Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}> <a>Delete</a> </Popconfirm> ) : null, }, ]; this.state = { dataSource: [ { key: '0', name: 'Edward King 0', age: '32', address: 'London, Park Lane no. 0', }, { key: '1', name: 'Edward King 1', age: '32', address: 'London, Park Lane no. 1', }, ], count: 2, }; } handleDelete = (key) => { const dataSource = [...this.state.dataSource]; this.setState({ dataSource: dataSource.filter((item) => item.key !== key), }); }; handleAdd = () => { const { count, dataSource } = this.state; const newData = { key: count, name: `Edward King ${count}`, age: '32', address: `London, Park Lane no. ${count}`, }; this.setState({ dataSource: [...dataSource, newData], count: count + 1, }); }; handleSave = (row) => { const newData = [...this.state.dataSource]; const index = newData.findIndex((item) => row.key === item.key); const item = newData[index]; newData.splice(index, 1, { ...item, ...row }); this.setState({ dataSource: newData, }); }; render() { const { dataSource } = this.state; const components = { body: { row: EditableRow, cell: EditableCell, }, }; const columns = this.columns.map((col) => { if (!col.editable) { return col; } return { ...col, onCell: (record) => ({ record, editable: col.editable, dataIndex: col.dataIndex, title: col.title, handleSave: this.handleSave, }), }; }); return ( <div> <Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16, }} > Add a row </Button> <Table components={components} rowClassName={() => 'editable-row'} bordered dataSource={dataSource} columns={columns} /> </div> ); } } ReactDOM.render(<EditableTable />, mountNode);
分析
class
可以看到這竟然是個class元件
建構函式
// 基本寫法
constructor(props) {
super(props);
...
}
this.columns
可以看到在構造函數了虛擬了列資料
this.columns = [ { title: 'name', dataIndex: 'name', width: '30%', editable: true, // 本列可編輯 }, { title: 'age', dataIndex: 'age', }, { title: 'address', dataIndex: 'address', }, { title: 'operation', dataIndex: 'operation', render: (_, record) => // 這裡的第二個引數record指代該行資料 this.state.dataSource.length >= 1 ? ( // 呼叫了state資料判斷,我們看完state和方法再回來。 <Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}> <a>Delete</a> </Popconfirm> // 該列固定戰術Delete字元,點選時彈出氣泡確認,確認刪除時傳入該行key呼叫this.handleDelete方法刪除 ) : null, }, ];
this.state
在這裡模擬了表單資料
this.state = { dataSource: [ { key: '0', name: 'Edward King 0', age: '32', address: 'London, Park Lane no. 0', }, { key: '1', name: 'Edward King 1', age: '32', address: 'London, Park Lane no. 1', }, ], count: 2, };
方法
handleDelete
刪除方法,看多了函式元件看class的setState有點不適應。
很常見的刪除原理:比對該行key,然後用 dataSource.filter()方法設定狀態
handleDelete = (key) => {
const dataSource = [...this.state.dataSource];
this.setState({
dataSource: dataSource.filter((item) => item.key !== key),
});
};
handleAdd
增加方法。在這裡模擬了一條新資料。
handleAdd = () => {
const { count, dataSource } = this.state;
const newData = {
key: count,
name: `Edward King ${count}`,
age: '32',
address: `London, Park Lane no. ${count}`,
};
this.setState({
dataSource: [...dataSource, newData],
count: count + 1,
});
};
handleSave
儲存方法。應該是可編輯單元格編輯後呼叫儲存吧。我們可以先去看render(){}內部
handleSave = (row) => {
const newData = [...this.state.dataSource];
const index = newData.findIndex((item) => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
this.setState({
dataSource: newData,
});
};
render(){}
這裡直接逐行註釋了
// 從state中解構出表單資料
const { dataSource } = this.state;
// components物件,描述了編輯行EditableRow方法與編輯單元格EditableRow方法
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
// 列陣列處理:不可編輯的列直接返回值;可編輯列增加**onCell方法**,該方法返回該單元格資料、editable等屬性、以及儲存方法
const columns = this.columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave: this.handleSave,
}),
};
});
// 返回一個增加行按鈕、一個table,tabe上有components屬性
return (
<div>
<Button
onClick={this.handleAdd}
type="primary"
style={{
marginBottom: 16,
}}
>
Add a row
</Button>
<Table
components={components}
rowClassName={() => 'editable-row'}
bordered
dataSource={dataSource}
columns={columns}
/>
</div>
);
EditableRow
// 建立一個Context物件
const EditableContext = React.createContext(null);
const EditableRow = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};