antd可编辑单元格实现原理


最近在项目复盘的时候,发现使用了antd可编辑单元格。用的时候没仔细看,现在详细看下实现原理。
antd可编辑单元格:https://ant.design/components/table-cn/#components-table-demo-edit-cell
项目复盘:

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 (
    
); }; 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 ? ( ) : (
{children}
); } return {childNode}; }; 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 ? ( this.handleDelete(record.key)}> Delete ) : 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 (
'editable-row'} bordered dataSource={dataSource} columns={columns} /> ); } } ReactDOM.render(, 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和方法再回来。
             this.handleDelete(record.key)}>
              Delete
             // 该列固定战术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 (
      
'editable-row'} bordered dataSource={dataSource} columns={columns} /> );

EditableRow

// 创建一个Context对象
const EditableContext = React.createContext(null);

const EditableRow = ({ index, ...props }) => {
  const [form] = Form.useForm();
  return (
    
); };

EditableCell