数据表格 - 编辑
数据表格内置支持单元格和行编辑。
功能完善的 CRUD
数据表格不仅是一个数据可视化工具。它还提供内置的编辑功能,用于管理您的数据集。以下演示展示了企业应用中常见的功能完善的 CRUD(创建、读取、更新、删除)操作。
使列可编辑
您可以通过在其列定义中启用 editable
属性来使列可编辑。
这允许用户编辑指定列中的任何单元格。例如,使用下面的代码片段,用户可以编辑 name
列中的单元格,但不能编辑 id
列中的单元格。
<DataGrid columns={[{ field: 'id' }, { field: 'name', editable: true }]} />
以下演示展示了如何使所有列都可编辑的示例。双击或按 Enter 键在任何单元格上进行尝试。
行编辑
默认情况下,一次只能编辑一个单元格。但是,您可以让用户同时编辑一行中的所有单元格。
要启用此行为,请将数据表格上的 editMode
属性设置为 "row"
。请注意,您仍然需要在每个列定义中设置 editable
属性,以指定哪些列是可编辑的;单元格编辑的基本规则也适用于行编辑。
<DataGrid editMode="row" columns={[{ field: 'name', editable: true }]} />
在编辑和视图模式之间切换
每个单元格和行都有两种模式:edit
和 view
。当处于 edit
模式时,用户可以直接更改单元格或行的内容。
开始编辑
当单元格处于 view
模式时,用户可以使用以下任何操作开始编辑单元格(或行,如果 editMode="row"
)
双击单元格
按 Enter、退格键 或 Delete 键——请注意,后两个选项都会删除任何现有内容
按任何可打印的键,例如 a、E、0 或 $
调用
apiRef.current.startCellEditMode
并传递要编辑的单元格的行 ID 和列字段apiRef.current.startCellEditMode({ id: 1, field: 'name' });
调用
apiRef.current.startRowEditMode
并传递行的 ID(仅在editMode="row"
时可用)。apiRef.current.startRowEditMode({ id: 1 });
停止编辑
当单元格处于 edit
模式时,用户可以使用以下任何交互停止编辑
按 Escape 键——这也会撤消任何已做的更改
按 Tab 键——这也会保存任何已做的更改
按 Enter 键——这也会保存任何已做的更改,并将焦点移动到同一列的下一个单元格
单击单元格或行外部——这也会保存任何已做的更改
调用
apiRef.current.stopCellEditMode({ id, field })
并传递已编辑的单元格的行 ID 和列字段apiRef.current.stopCellEditMode({ id: 1, field: 'name' }); // or apiRef.current.stopCellEditMode({ id: 1, field: 'name', ignoreModifications: true, // will also discard the changes made });
调用
apiRef.current.stopRowEditMode
并传递行的 ID(仅在editMode="row"
时可能)。apiRef.current.stopRowEditMode({ id: 1 }); // or apiRef.current.stopRowEditMode({ id: 1, ignoreModifications: true, // will also discard the changes made });
编辑事件
开始和停止编辑的交互分别触发 'cellEditStart'
和 'cellEditStop'
事件。对于行编辑,事件为 'rowEditStart'
和 'rowEditStop'
。您可以控制如何处理这些事件以自定义编辑行为。
为方便起见,您还可以使用它们各自的属性来监听这些事件
onCellEditStart
onCellEditStop
onRowEditStart
onRowEditStop
这些事件和属性在被调用时会带有一个对象,其中包含正在编辑的单元格的行 ID 和列字段。该对象还包含一个 reason
参数,用于指定哪种类型的交互触发了该事件——例如,当双击启动编辑模式时,'cellDoubleClick'
。
以下演示展示了如何在单击单元格外部时阻止用户退出编辑模式。为此,使用 onCellEditStop
属性来检查 reason
是否为 'cellFocusOut'
。如果条件为真,则禁用默认事件行为。在这种情况下,用户只能通过按 Enter、Escape 或 Tab 键来停止编辑单元格。
禁用行内特定单元格的编辑
editable
属性控制列级别上哪些单元格是可编辑的。您可以使用 isCellEditable
回调属性来定义用户可以在给定行中编辑哪些单独的单元格。它在被调用时会带有一个 GridCellParams
对象,如果单元格可编辑,则必须返回 true
,否则返回 false
。
在以下演示中,只有 Age
值为偶数的行是可编辑的。可编辑的单元格具有绿色背景,以提高可见性。
服务端持久化
processRowUpdate
回调
当用户执行操作以停止编辑时,会触发 processRowUpdate
回调。使用它将新值发送到服务器,并将它们保存到数据库或其他存储方法中。回调在被调用时会带有三个参数
- 已更新的行,其中包含
valueSetter
返回的新值。 - 编辑前行的原始值。
- 一个包含其他属性的对象,例如
rowId
。
请注意,processRowUpdate
必须返回行对象以更新数据表格的内部状态。返回的值稍后将用作调用 apiRef.current.updateRows
时的参数。
<DataGrid
rows={rows}
columns={columns}
processRowUpdate={(updatedRow, originalRow) =>
mySaveOnServerFunction(updatedRow);
}
onProcessRowUpdateError={handleProcessRowUpdateError}
/>
如果您想从数据表格的内部状态中删除一行,则可以在来自 processRowUpdate
回调的行对象中返回一个附加属性 _action: 'delete'
。这将从数据表格的内部状态中删除该行。与更新rows
属性或使用 setRows
API 方法相比,这是一种性能更高的删除行的方式,因为 processRowUpdate
在底层使用了 updateRows
,这不会导致行树的完全重新生成。
<DataGrid
{...otherProps}
processRowUpdate={(updatedRow, originalRow) => {
if (shouldDeleteRow(updatedRow)) {
return { ...updatedRow, _action: 'delete' };
}
return updatedRow;
}}
/>
在上面的示例中,shouldDeleteRow
是一个函数,用于确定是否应根据更新后的行数据删除行。如果 shouldDeleteRow
返回 true
,则该行将从数据表格的内部状态中删除。
服务端验证
如果您需要在 processRowUpdate
上取消保存过程——例如,当数据库验证失败或用户想要拒绝更改时——有两种选择
- 拒绝 Promise,以便内部状态不更新,并且单元格保持编辑模式。
- 使用第二个参数(编辑前的原始行)解析 Promise,以便内部状态不更新,并且单元格退出编辑模式。
以下演示实现了第一个选项:拒绝 Promise。它不是在验证时进行验证,而是在服务器上模拟验证。如果新名称为空,则负责保存行的 Promise 将被拒绝,并且单元格将保持编辑模式。
该演示还显示了 processRowUpdate
可以预处理将要保存到内部状态的行模型。
此外,调用 onProcessRowUpdateError
来显示错误消息。
要退出编辑模式,请按 Escape 键或输入有效的名称。
保存前确认
第二个选项——使用第二个参数解析 Promise——让用户可以通过拒绝更改并退出编辑模式来取消保存过程。在这种情况下,processRowUpdate
会使用行的原始值解析。
以下演示展示了如何使用此方法在将数据发送到服务器之前请求确认。如果用户接受更改,则内部状态将使用这些值进行更新。但是,如果更改被拒绝,则内部状态保持不变,并且单元格将恢复为其原始值。该演示还使用了验证来防止输入空名称。
值解析器和值设置器
您可以使用列定义中的 valueParser
属性来修改用户输入的值——例如,将值转换为不同的格式
const columns: GridColDef[] = [
{
valueParser: (value, row, column, apiRef) => {
return value.toLowerCase();
},
},
];
您可以使用列定义的 valueSetter
属性来自定义如何使用新值更新行。这使您可以从嵌套对象中插入值。它在被调用时会带有一个对象,其中包含要保存的新单元格值以及单元格所属的行。如果您已经使用 valueGetter
从嵌套对象中提取值,那么可能也需要 valueSetter
。
const columns: GridColDef[] = [
{
valueSetter: (value, row) => {
const [firstName, lastName] = value!.toString().split(' ');
return { ...row, firstName, lastName };
},
},
];
在以下演示中,valueParser
和 valueSetter
都为“Full name”列定义。valueParser
将输入的值大写,valueSetter
将值拆分并将其正确保存到行模型中
验证
如果列定义为 preProcessEditCellProps
属性设置了回调,则每次从该列的单元格中输入新值时都会调用它。此属性允许您预处理传递给编辑组件的属性。preProcessEditCellProps
回调在被调用时会带有一个对象,其中包含以下属性
id
:行 IDrow
:行模型,其中包含进入编辑模式之前单元格或行的值props
:属性,其中包含值解析器之后的值,这些属性被传递给编辑组件hasChanged
:确定props.value
是否与上次调用此回调时不同
数据验证是以这种方式完成的预处理类型之一。要验证输入的数据,请将回调传递给 preProcessEditCellProps
,检查 props.value
是否有效。如果新值无效,请将 props.error
设置为真值并返回修改后的属性,如下例所示。当用户尝试保存更新后的值时,如果 error 属性为真值(无效),则更改将被拒绝。
const columns: GridColDef[] = [
{
field: 'firstName',
preProcessEditCellProps: (params: GridPreProcessEditCellProps) => {
const hasError = params.props.value.length < 3;
return { ...params.props, error: hasError };
},
},
];
下面的演示包含服务端数据验证的示例。在这种情况下,回调返回一个 Promise,该 Promise 解析为修改后的属性。请注意,传递给 props.error
的值直接作为 error
属性传递给编辑组件。在 Promise 未解析时,编辑组件将收到一个 isProcessingProps
属性,其值等于 true
。
受控模型
您可以使用属性 cellModesModel
和 rowModesModel
来控制活动模式(仅在 editMode="row"
时有效)。
cellModesModel
属性接受一个对象,其中包含给定行中给定列字段的 mode
(以及其他选项),如下例所示。接受的选项与apiRef.current.startCellEditMode
和 apiRef.current.stopCellEditMode
中可用的选项相同。
// Changes the mode of field=name from row with id=1 to "edit"
<DataGrid
cellModesModel={{ 1: { name: { mode: GridCellModes.Edit } } }}
/>
// Changes the mode of field=name from row with id=1 to "view", ignoring modifications made
<DataGrid
cellModesModel={{ 1: { name: { mode: GridCellModes.View, ignoreModifications: true } } }}
/>
对于行编辑,rowModesModel
属性的工作方式类似。接受的选项与apiRef.current.startRowEditMode
和 apiRef.current.stopRowEditMode
中可用的选项相同。
// Changes the mode of the row with id=1 to "edit"
<DataGrid
editMode="row"
rowModesModel={{ 1: { mode: GridRowModes.Edit } }}
/>
// Changes the mode of the row with id=1 to "view", ignoring modifications made
<DataGrid
editMode="row"
rowModesModel={{ 1: { mode: GridRowModes.View, ignoreModifications: true } }}
/>
此外,回调属性 onCellModesModelChange
和 onRowModesModelChange
(仅在 editMode="row"
时有效)可用。使用它们来更新各自的属性。
在下面的演示中,cellModesModel
用于使用外部按钮控制所选单元格的模式。有关使用行编辑的示例,请查看功能完善的 CRUD 组件。
创建您自己的编辑组件
每个内置的列类型都提供一个组件来编辑单元格的值。要自定义列类型或覆盖现有组件,您可以通过列定义中的 renderEditCell
属性提供一个新的编辑组件。此属性的工作方式类似于 renderCell
属性,不同之处在于它在单元格处于编辑模式时呈现。
function CustomEditComponent(props: GridRenderEditCellParams) {
return <input type="text" value={params.value} onChange={...} />;
}
const columns: GridColDef[] = [
{
field: 'firstName',
renderEditCell: (params: GridRenderEditCellParams) => (
<CustomEditComponent {...params} />
),
},
];
renderEditCell
属性接收来自 GridRenderEditCellParams
的所有参数,后者扩展了 GridCellParams
。此外,预处理期间添加的属性也在参数中可用。以下是要考虑的最重要的参数
value
:包含编辑模式下单元格的当前值,覆盖来自GridCellParams
的值error
:验证期间添加的错误isProcessingProps
:preProcessEditCellProps
是否正在执行
一旦在输入中输入新值,就必须将其发送到数据表格。为此,请将行 ID、列字段和新的单元格值传递给对 apiRef.current.setEditCellValue
的调用。新值将被解析和验证,并且 value
属性将在下一次渲染中反映更改。
重要的是还要处理自定义编辑组件的可访问性。当单元格进入编辑模式时,必须聚焦一个元素,以便通过键盘和屏幕阅读器进行访问。由于多个单元格可能同时处于编辑模式,因此 hasFocus
属性将在应具有焦点的单元格上为 true
。使用此属性聚焦适当的元素。
function CustomEditComponent(props: GridRenderEditCellParams) {
const { id, value, field, hasFocus } = props;
const apiRef = useGridApiContext();
const ref = React.useRef();
React.useLayoutEffect(() => {
if (hasFocus) {
ref.current.focus();
}
}, [hasFocus]);
const handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value; // The new value entered by the user
apiRef.current.setEditCellValue({ id, field, value: newValue });
};
return <input ref={ref} type="text" value={value} onChange={handleValueChange} />;
}
以下演示为“Rating”列实现了一个自定义编辑组件,该组件基于 Rating
组件。
使用防抖
默认情况下,每次调用 apiRef.current.setEditCellValue
都会触发新的渲染。如果编辑组件要求用户键入新值,则过于频繁地重新渲染数据表格会大大降低性能。避免这种情况的一种方法是防抖 API 调用。您可以使用 apiRef.current.setEditCellValue
通过将 debounceMs
参数设置为一个正整数来处理防抖,该正整数定义了以毫秒为单位的设定时间段。无论 API 方法被调用多少次,数据表格都只会在该时间段过去后重新渲染。
apiRef.current.setEditCellValue({ id, field, value: newValue, debounceMs: 200 });
当数据表格仅设置为在给定时间段过去后重新渲染时,value
属性不会在每次 apiRef.current.setEditCellValue
调用时更新。为避免 UI 卡顿,编辑组件可以将当前值保存在内部状态中,并在 value
更改时同步它。修改编辑组件以启用此功能
function CustomEditComponent(props: GridRenderEditCellParams) {
- const { id, value, field } = props;
+ const { id, value: valueProp, field } = props;
+ const [value, setValue] = React.useState(valueProp);
const apiRef = useGridApiContext();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value; // The new value entered by the user
- apiRef.current.setEditCellValue({ id, field, value: newValue });
+ apiRef.current.setEditCellValue({ id, field, value: newValue, debounceMs: 200 });
+ setValue(newValue);
};
+ React.useEffect(() => {
+ setValue(valueProp);
+ }, [valueProp]);
+
return <input type="text" value={value} onChange={handleChange} />;
}
使用自动停止
当编辑组件在值更改后立即停止编辑模式时,它具有“自动停止”行为。为了更好地理解,想象一个带有组合框的编辑组件,按照正常的步骤创建。默认情况下,它需要两次单击才能更改单元格的值:一次单击单元格内部以选择新值,另一次单击单元格外部以保存。如果第一次单击也停止编辑模式,则可以避免第二次单击。要创建具有自动停止功能的编辑组件,请在设置新值后调用 apiRef.current.stopCellEditMode
。由于 apiRef.current.setEditCellValue
可能会进行额外的处理,因此您必须等待它解析后再停止编辑模式。此外,最好检查 apiRef.current.setEditCellValue
是否返回了 true
。如果 preProcessEditProps
在验证期间设置了错误,则它将为 false
。
const handleChange = async (event: SelectChangeEvent) => {
const isValid = await apiRef.current.setEditCellValue({
id,
field,
value: event.target.value,
});
if (isValid) {
apiRef.current.stopCellEditMode({ id, field });
}
};
以下演示为“Role”列实现了一个具有自动停止功能的编辑组件,该组件基于原生 Select
组件。
getCellMode()
获取单元格的模式。
签名
getCellMode: (id: GridRowId, field: string) => GridCellMode
getRowMode()
获取行的模式。
签名
getRowMode: (id: GridRowId) => GridRowMode
getRowWithUpdatedValues()
返回通过编辑单元格设置的值的行。在行编辑中,field
被忽略,并且考虑所有字段。
签名
getRowWithUpdatedValues: (id: GridRowId, field: string) => GridRowModel
isCellEditable()
控制单元格是否可编辑。
签名
isCellEditable: (params: GridCellParams) => boolean
setEditCellValue()
设置编辑单元格的值。通常在编辑单元格组件内部使用。
签名
setEditCellValue: (params: GridEditCellValueParams, event?: MuiBaseEvent) => Promise<boolean> | void
startCellEditMode()
将与给定行 id 和字段对应的单元格置于编辑模式。
签名
startCellEditMode: (params: GridStartCellEditModeParams) => void
startRowEditMode()
将与给定 id 对应的行置于编辑模式。
签名
startRowEditMode: (params: GridStartRowEditModeParams) => void
stopCellEditMode()
将与给定行 id 和字段对应的单元格置于视图模式,并使用存储的新值更新原始行。如果 params.ignoreModifications
为 true
,则将放弃所做的修改。
签名
stopCellEditMode: (params: GridStopCellEditModeParams) => void
stopRowEditMode()
将与给定 id 对应的行置于视图模式,并使用存储的新值更新原始行。如果 params.ignoreModifications
为 true
,则将放弃所做的修改。
签名
stopRowEditMode: (params: GridStopRowEditModeParams) => void