跳到內容
+

Data Grid - 編輯

Data Grid 內建支援儲存格和列編輯功能。

Data Grid 不僅僅是一個資料視覺化工具。它提供內建的編輯功能,讓您管理您的資料集。以下示範展示了企業應用程式中常見的全功能 CRUD(建立、讀取、更新、刪除)。

使欄位可編輯

您可以透過在其欄定義中啟用 editable 屬性,使欄位可編輯。

這讓使用者可以編輯指定欄位中的任何儲存格。例如,使用下面的程式碼片段,使用者可以編輯 name 欄位中的儲存格,但不能編輯 id 欄位中的儲存格。

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

以下示範顯示了如何使所有欄位都可編輯的範例。透過雙擊或按下 Enter 鍵來操作任何儲存格。

列編輯

預設情況下,一次只能編輯一個儲存格。但是您可以讓使用者同時編輯列中的所有儲存格。

要啟用此行為,請將 Data Grid 上的 editMode 屬性設定為 "row"。請注意,您仍然需要在每個欄定義中設定 editable 屬性,以指定哪些欄位是可編輯的;儲存格編輯的基本規則也適用於列編輯。

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

以下示範說明了列編輯的工作原理。使用者可以使用與儲存格編輯提供的相同操作(例如雙擊儲存格)來開始停止編輯列。

在編輯和檢視模式之間切換

每個儲存格和列都有兩種模式:editview。當處於 edit 模式時,使用者可以直接更改儲存格或列的內容。

開始編輯

當儲存格處於 view 模式時,使用者可以使用以下任何操作開始編輯儲存格(或列,如果 editMode="row"):

  • 雙擊儲存格

  • 按下 EnterBackspaceDelete 鍵—請注意,後兩個選項都會刪除任何現有內容

  • 按下任何可列印的鍵,例如 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 必須傳回列物件以更新 Data Grid 內部狀態。傳回的值稍後將用作呼叫 apiRef.current.updateRows 的參數。

<DataGrid
  rows={rows}
  columns={columns}
  processRowUpdate={(updatedRow, originalRow) =>
    mySaveOnServerFunction(updatedRow);
  }
  onProcessRowUpdateError={handleProcessRowUpdateError}
/>

如果您想從 Data Grid 的內部狀態中刪除列,您可以在來自 processRowUpdate 回呼的列物件中傳回額外的屬性 _action: 'delete'。這將從 Data Grid 的內部狀態中刪除列。與更新rows 屬性或使用 setRows API 方法相比,這是一種效能更高的刪除列的方法,因為 processRowUpdate 在底層使用了 updateRows,這不會導致列樹的完全重新生成。

<DataGrid
  {...otherProps}
  processRowUpdate={(updatedRow, originalRow) => {
    if (shouldDeleteRow(updatedRow)) {
      return { ...updatedRow, _action: 'delete' };
    }
    return updatedRow;
  }}
/>

在上面的範例中,shouldDeleteRow 是一個函數,它根據更新後的列資料確定是否應刪除列。如果 shouldDeleteRow 傳回 true,則會從 Data Grid 的內部狀態中刪除列。

伺服器端驗證

如果您需要在 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 };
    },
  },
];

在以下示範中,全名欄位同時定義了 valueParservalueSettervalueParser 將輸入的值大寫,而 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 是否正在執行

一旦在輸入中輸入新值,就必須將其傳送到 Data Grid。為此,請將列 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} />;
}

以下示範實作了一個自訂編輯組件,該組件基於來自 @mui/materialRating 組件,用於 Rating 欄位。

具有延遲防抖

預設情況下,每次呼叫 apiRef.current.setEditCellValue 都會觸發新的呈現。如果編輯組件需要使用者輸入新值,則過於頻繁地重新呈現 Data Grid 會大幅降低效能。避免這種情況的一種方法是延遲防抖 API 呼叫。您可以透過將 debounceMs 參數設定為正整數(以毫秒為單位定義設定的時間段)來使用 apiRef.current.setEditCellValue 來處理延遲防抖。無論 API 方法被呼叫多少次,Data Grid 都只會在該時間段過去後才重新呈現。

apiRef.current.setEditCellValue({ id, field, value: newValue, debounceMs: 200 });

當 Data Grid 僅設定為在給定時間段過去後才重新呈現時,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