Data Grid - 編輯
Data Grid 內建支援儲存格和列編輯功能。
全功能 CRUD
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 }]} />
在編輯和檢視模式之間切換
每個儲存格和列都有兩種模式:edit
和 view
。當處於 edit
模式時,使用者可以直接更改儲存格或列的內容。
開始編輯
當儲存格處於 view
模式時,使用者可以使用以下任何操作開始編輯儲存格(或列,如果 editMode="row"
):
雙擊儲存格
按下 Enter、Backspace 或 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
必須傳回列物件以更新 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
上取消儲存程序—例如,當資料庫驗證失敗或使用者想要拒絕更改時—有兩個選項
- 拒絕 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
。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
是否正在執行
一旦在輸入中輸入新值,就必須將其傳送到 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/material
的 Rating
組件,用於 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
組件。
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