一些题外话
我最近有快2个月没有更新博客了,不是我已经忘记了这件事,一个原因是最近一直没有遇到什么值得记录的问题以及知识点。如果要硬写也不是写不出来,毕竟自己每天也有看别人的博客或者知识点。只是我认为这些东西就算写出来80%也是搬运别人的,没有太大的意义。另外一个原因是我的小闺女刚出生,事情有点多。
进入正题
因为新的需求中需要用到可编辑单元格的表格,所以就阅读的antd的文档看有没有合理的实现方案。
对于antd中的Table组件大家应该都不陌生,功能非常齐全且强大,使用起来也比较简单。其中就有一个例子是对于自定义单元格的使用的:
虽然当时看到有现成的功能很开心,但是读了对应的代码之后就觉得这个实现好复杂,需要完全自定义rowComponent和cellComponent,同时还需要用到React中的上下文Context,这个使用起来更麻烦,而且也会使得数据流变的奇怪。所以我希望有一种更加简单并且容易理解的方式,我想到一个方案:是否可以对column配置项中的render函数进行自定义,返回一个可以编辑的div就可以,然后再实现一些与父组件的交互就可以了。
经过实践过后完全可行,下面上代码:
TableTest.tsx
import { Table } from 'antd';
import React, { useEffect, useState } from 'react';
import EditDiv from './EditDiv';
const TableTest = () => {
const [data, setData] = useState<Record<keyof any,any>[]>([]);
useEffect(() => {
const updateData = [
{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0',
operation: 'London, Park Lane no. 0',
},
{
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1',
operation: 'London, Park Lane no. 1',
},
]
setData(updateData);
}, [])
const updateData = (val) => {
// 此处模拟异步请求,需要将对应的promise对象返回,在EditDiv文件中会进行处理
const promise = new Promise<boolean>((resolve) => {
setTimeout(() => {
resolve(true)
}, 1000)
});
return promise;
}
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '30%',
editable: true,
},
{
title: 'age',
dataIndex: 'age',
},
{
title: 'address',
dataIndex: 'address',
},
{
title: 'operation',
dataIndex: 'operation',
render: (text) => {
return <EditDiv value={text} onValueChange={updateData} />
}
},
];
return (
<Table
dataSource={data}
columns={columns}
/>
)
}
export default TableTest;
这个文件中的columns的render最终返回的是一个EditDiv组件:一个可以编辑的div。
EditDiv.tsx
import React, { useEffect, useReducer, useRef, useState } from 'react';
interface IEditDivProps{
value: string | number;
onValueChange?: (val: string) => Promise<boolean>;
}
const reducer = (state, { type, payload }) => {
switch(type) {
case 'update':
return {
...state,
...payload
}
case 'reset':
return {
...state,
isEdit: false
}
default:
return {
...state
}
}
}
const EditDiv = ({ value, onValueChange }:IEditDivProps) => {
const initState = {
isEdit: false,
val: value,
loading: false
}
// 此处使用了useReducer,这个只是自己想用,其实也可以分别拆开使用
const [state, setState] = useReducer(reducer, initState);
const inputRef = useRef<HTMLInputElement>(null);
// 当进入编辑状态input自动获取焦点
useEffect(() => {
if(state.isEdit && inputRef.current) {
inputRef.current.focus();
}
}, [state.isEdit])
const handleVal = (e) => {
setState({type: 'update', payload: { val: e.target.value }});
}
const showInput = () => {
setState({type: 'update', payload: { isEdit: true }});
}
// 处理valueChange中返回的promise对象,如果返回true则代表服务已经修改成功,需要前端相应修改
// 如果为false则需要将input还原为原来的值
const hideInput = async () => {
if(onValueChange) {
setState({type: 'update', payload: { loading: true }});
const res = await onValueChange(state.val);
if(res) {
setState({type: 'update', payload: { loading: false, isEdit: false }});
} else {
setState({type: 'update', payload: { loading: false, val: value, isEdit: false }});
}
}else {
setState({type: 'update', payload: { isEdit: false }});
}
}
// 监听回车事件
const handleKey = (e) => {
if(e.keyCode === 13) {
hideInput();
}
}
const renderElement = () => {
const { val, isEdit, loading } = state;
if(isEdit) {
if(loading) {
return 'loading...'
} else {
return (
<input
ref={inputRef}
value={val}
onChange={handleVal}
onBlur={hideInput}
onKeyUp={handleKey}
/>
)
}
}else {
return (
<div onClick={showInput} style={{height: "1em", cursor: 'pointer'}}>{val}</div>
)
}
}
return (
<div>
{renderElement()}
</div>
)
}
export default EditDiv;
结语
好了,以上就是我根据自己的思路实现的自定义表格,我本地试了之后是完全可行的,就是还没有添加css样式,所以看起来有点丑,但是影响不是很大。希望对阅读的各位有所帮助,如果帮到了你,希望能点赞支持一下。