跳到主要内容
+

数据表格 - 编辑

数据表格内置支持单元格和行编辑。

数据表格不仅是一个数据可视化工具。它还提供内置的编辑功能,用于管理您的数据集。以下演示展示了企业应用中常见的功能完善的 CRUD(创建、读取、更新、删除)操作。

使列可编辑

您可以通过在其列定义中启用 editable 属性来使列可编辑。

这允许用户编辑指定列中的任何单元格。例如,使用下面的代码片段,用户可以编辑 name 列中的单元格,但不能编辑 id 列中的单元格。

<DataGrid columns={[{ field: 'id' }, { field: 'name', editable: true }]} />

以下演示展示了如何使所有列都可编辑的示例。双击或按 Enter 键在任何单元格上进行尝试。

行编辑

默认情况下,一次只能编辑一个单元格。但是,您可以让用户同时编辑一行中的所有单元格。

要启用此行为,请将数据表格上的 editMode 属性设置为 "row"。请注意,您仍然需要在每个列定义中设置 editable 属性,以指定哪些列是可编辑的;单元格编辑的基本规则也适用于行编辑。

<DataGrid editMode="row" columns={[{ field: 'name', editable: true }]} />

以下演示说明了行编辑的工作方式。用户可以使用与单元格编辑相同的操作(例如双击单元格)来开始停止编辑行。

在编辑和视图模式之间切换

每个单元格和行都有两种模式:editview。当处于 edit 模式时,用户可以直接更改单元格或行的内容。

开始编辑

当单元格处于 view 模式时,用户可以使用以下任何操作开始编辑单元格(或行,如果 editMode="row"

  • 双击单元格

  • Enter退格键Delete 键——请注意,后两个选项都会删除任何现有内容

  • 按任何可打印的键,例如 aE0$

  • 调用 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'。如果条件为真,则禁用默认事件行为。在这种情况下,用户只能通过按 EnterEscapeTab 键来停止编辑单元格。

Enter 键开始编辑

禁用行内特定单元格的编辑

editable 属性控制列级别上哪些单元格是可编辑的。您可以使用 isCellEditable 回调属性来定义用户可以在给定行中编辑哪些单独的单元格。它在被调用时会带有一个 GridCellParams 对象,如果单元格可编辑,则必须返回 true,否则返回 false

在以下演示中,只有 Age 值为偶数的行是可编辑的。可编辑的单元格具有绿色背景,以提高可见性。

Enter 键开始编辑

服务端持久化

processRowUpdate 回调

当用户执行操作以停止编辑时,会触发 processRowUpdate 回调。使用它将新值发送到服务器,并将它们保存到数据库或其他存储方法中。回调在被调用时会带有三个参数

  1. 已更新的行,其中包含 valueSetter 返回的新值。
  2. 编辑前行的原始值。
  3. 一个包含其他属性的对象,例如 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 上取消保存过程——例如,当数据库验证失败或用户想要拒绝更改时——有两种选择

  1. 拒绝 Promise,以便内部状态不更新,并且单元格保持编辑模式。
  2. 使用第二个参数(编辑前的原始行)解析 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 };
    },
  },
];

在以下演示中,valueParservalueSetter 都为“Full name”列定义。valueParser 将输入的值大写,valueSetter 将值拆分并将其正确保存到行模型中

验证

如果列定义为 preProcessEditCellProps 属性设置了回调,则每次从该列的单元格中输入新值时都会调用它。此属性允许您预处理传递给编辑组件的属性。preProcessEditCellProps 回调在被调用时会带有一个对象,其中包含以下属性

  • id:行 ID
  • row:行模型,其中包含进入编辑模式之前单元格或行的值
  • 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

受控模型

您可以使用属性 cellModesModelrowModesModel 来控制活动模式(仅在 editMode="row" 时有效)。

cellModesModel 属性接受一个对象,其中包含给定行中给定列字段的 mode(以及其他选项),如下例所示。接受的选项与apiRef.current.startCellEditModeapiRef.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.startRowEditModeapiRef.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 } }}
/>

此外,回调属性 onCellModesModelChangeonRowModesModelChange(仅在 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:验证期间添加的错误
  • isProcessingPropspreProcessEditCellProps 是否正在执行

一旦在输入中输入新值,就必须将其发送到数据表格。为此,请将行 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 组件。

高级用例

编辑技巧”页面涵盖了更高级的用例,例如

apiRef

网格公开了一组方法,这些方法使用命令式 apiRef 启用所有这些功能。要了解有关如何使用它的更多信息,请查看 API 对象部分。

签名
getCellMode: (id: GridRowId, field: string) => GridCellMode
签名
getRowMode: (id: GridRowId) => GridRowMode
签名
getRowWithUpdatedValues: (id: GridRowId, field: string) => GridRowModel
签名
isCellEditable: (params: GridCellParams) => boolean
签名
setEditCellValue: (params: GridEditCellValueParams, event?: MuiBaseEvent) => Promise<boolean> | void
签名
startCellEditMode: (params: GridStartCellEditModeParams) => void
签名
startRowEditMode: (params: GridStartRowEditModeParams) => void
签名
stopCellEditMode: (params: GridStopCellEditModeParams) => void
签名
stopRowEditMode: (params: GridStopRowEditModeParams) => void