快速挂载卸载Material UI DataGrid报错:无法更新已卸载组件状态
我在React应用中使用Material UI的DataGrid组件时,遇到了一个问题:当快速挂载、卸载该组件(比如快速切换标签页或通过React Router DOM导航),控制台会抛出警告:Warning: Can't perform a React state update on an unmounted component.。组件本身渲染正常,我没有在组件内执行异步操作,仅通过传入新数据更新状态。已经使用了最新版本的@mui/datagrid和@mui/material,尝试过用挂载状态判断跳过setState调用、useRef存储数据+useState强制更新等方案,但都没解决,怀疑是DataGrid组件内部的问题。
原组件代码
import { Button, Grid, Stack, Tooltip, Typography, useTheme, } from '@mui/material'; import { DataGrid } from '@mui/x-data-grid'; import React from 'react'; import AssignmentTurnedInIcon from '@mui/icons-material/AssignmentTurnedIn'; import { CustomDatagridPagination } from 'app/components/CustomDatagridPagination'; export const TemplatesTable = ({ rows, onOpenAssignationsModal }) => { const theme = useTheme(); const templateColumns = [ { field: 'name', headerName: 'Plantilla', flex: 1, renderCell: ({ row }) => { return ( <Grid container sx={{ display: 'flex', flexDirection: 'column', marginY: 2 }} > <Typography marginBottom={1} fontWeight={'bold'}> {row.name} </Typography> <Typography sx={{ color: theme.palette.grey[600] }}> {row.description} </Typography> </Grid> ); }, }, { field: 'questions', headerName: 'Preguntas', valueGetter: ({ value }) => { if (value) { return value.items.length; } else { return 0; } }, }, { field: 'assignments', headerName: 'Asignaciones', valueGetter: ({ value }) => { if (value) { return value.length; } else { return 0; } }, }, { field: 'actions', headerName: 'Acciones', disableColumnMenu: true, sortable: false, filtrable: false, renderCell: ({ row }) => { return ( <Grid container> <Tooltip title='Ver asignaciones' arrow> <Button onClick={() => onOpenAssignationsModal(row)}> <AssignmentTurnedInIcon /> </Button> </Tooltip> </Grid> ); }, }, ]; return ( <React.Fragment> <DataGrid columns={templateColumns} rows={rows || []} rowSelection={false} sx={{ '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { outline: 'none !important', }, '& .MuiDataGrid-virtualScroller::-webkit-scrollbar': { width: '0.4em', }, '& .MuiDataGrid-virtualScroller::-webkit-scrollbar-track': { background: '#f1f1f1', }, '& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb': { backgroundColor: '#999', }, '& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb:hover': { background: '#555', }, }} pageSizeOptions={[5, 10, 25]} getRowHeight={() => 'auto'} disableRowSelectionOnClick initialState={{ pagination: { paginationModel: { pageSize: 5 } }, }} slots={{ pagination: CustomDatagridPagination, }} components={{ NoRowsOverlay: () => ( <Stack height='100%' alignItems='center' justifyContent='center'> No se encontraron plantillas </Stack> ), }} /> </React.Fragment> ); };
尝试过的无效解决方案
// 省略重复代码部分 const dataRef = useRef([]); const [forceUpdate, setForceUpdate] = useState(false); const [isMounted, setIsMounted] = useState(false); useEffect(() => { dataRef.current = rows; // Actualiza la referencia con los nuevos datos setIsMounted(true); if (isMounted) { setForceUpdate((prev) => !prev); } return () => { setIsMounted(false); }; }, [rows]); return ( <React.Fragment key={forceUpdate}> <DataGrid columns={templateColumns} rows={dataRef.current} rowSelection={false} // 省略后续重复代码
可行解决方案
方案1:通过key属性强制DataGrid重建
当组件快速卸载挂载时,给DataGrid设置一个随挂载状态变化的key,强制组件完全重建,避免内部残留的状态更新逻辑触发警告:
export const TemplatesTable = ({ rows, onOpenAssignationsModal }) => { // ... 保留原有的theme、templateColumns等代码 // 使用时间戳作为key,确保每次挂载时DataGrid都是新实例 const gridKey = useRef(Date.now()); useEffect(() => { gridKey.current = Date.now(); }, []); return ( <DataGrid key={gridKey.current} columns={templateColumns} rows={rows || []} // ... 保留原有的其他props /> ); };
方案2:利用AbortController清理内部异步操作
DataGrid内部可能存在未被处理的异步更新,通过AbortController在组件卸载时终止这些操作:
export const TemplatesTable = ({ rows, onOpenAssignationsModal }) => { // ... 保留原有的theme、templateColumns等代码 const abortController = useRef(new AbortController()); useEffect(() => { const controller = abortController.current; return () => { controller.abort(); }; }, []); return ( <DataGrid columns={templateColumns} rows={rows || []} // ... 保留原有的其他props /> ); };
方案3:调整MUI版本
如果确认是DataGrid的内部bug,可以尝试降级到已知稳定的版本(比如@mui/x-data-grid@6.18.0),或者关注MUI官方issue追踪修复进度,待修复后升级到最新版本。
方案4:用React.memo优化渲染并配合清理逻辑
将组件包裹在React.memo中减少不必要的重渲染,同时确保组件卸载时清理相关状态:
export const TemplatesTable = React.memo(({ rows, onOpenAssignationsModal }) => { // ... 原组件代码 useEffect(() => { return () => { // 组件卸载时的清理逻辑 }; }, []); return ( <DataGrid key={JSON.stringify(rows)} // 当rows内容变化时强制重建 // ... 原props /> ); });
为什么之前的方案无效
你尝试的isMounted判断方案无效,是因为setIsMounted(true)是异步更新,在同一次useEffect执行中isMounted仍然是初始的false,导致setForceUpdate不会触发。而useRef存储数据的方式无法解决DataGrid内部的状态更新问题——组件卸载后,DataGrid内部的钩子依然可能在执行状态更新,从而抛出警告。
内容的提问来源于stack exchange,提问作者Willy Antunez

