如何解决涉及FiberNode与performWorkUntilDeadline的React内存泄漏问题
React 应用内存泄漏问题:FiberNode 持续堆积
问题现象
- UI每次变更时内存持续增长,无错误或警告提示
- 未发现游离DOM元素,保留对象以大量
FiberNode为主 - Edge开发者工具显示这些
FiberNode被根节点的performWorkUntilDeadline函数持有,未直接调用过该函数 - 排查组件挂载/卸载次数,最多仅多一次,无重复挂载异常
- 等待垃圾回收、隔离交互操作后问题仍存在,所有UI交互均可复现(例:拖拽调整MUI Drawer宽度)
复现代码
import React from 'react'; import './App.css'; import { styled } from '@mui/material/styles'; import { Transitions } from '@mui/material'; import { Drawer } from '@mui/material'; import { MUIStyledCommonProps } from '@mui/system'; export const shouldUseDevStreams = process.env.NODE_ENV == 'development'; export default function App() { const minDrawerWidth = 258; const maxDrawerWidth = 2000; const defaultDrawerWidth = 258; const [leftDrawerWidth, setLeftDrawerWidth] = React.useState(defaultDrawerWidth); const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'leftOpen' })(({ theme }: MUIStyledCommonProps) => ({ flexGrow: 1, ...{ transition: (theme?.transitions as Transitions).create('margin', { easing: (theme?.transitions as Transitions).easing.easeOut, duration: (theme?.transitions as Transitions).duration.enteringScreen }), marginLeft: `${leftDrawerWidth}px` } })); const handleLeftDrawerMouseDown = () => { document.addEventListener('mouseup', handleLeftDrawerMouseUp, true); document.addEventListener('mousemove', handleLeftDrawerMouseMove, true); }; const handleLeftDrawerMouseUp = () => { document.removeEventListener('mouseup', handleLeftDrawerMouseUp, true); document.removeEventListener('mousemove', handleLeftDrawerMouseMove, true); }; const handleLeftDrawerMouseMove = (e: { clientX: number }) => { const newWidth = e.clientX - document.body.offsetLeft + 2; const maxWidth = Math.min(maxDrawerWidth, document.body.offsetWidth); if (newWidth > minDrawerWidth && newWidth < maxWidth) { setLeftDrawerWidth(newWidth); } }; return ( <React.Fragment> <Drawer data-testid="leftDrawer" variant="persistent" anchor="left" open={true} sx={{ width: leftDrawerWidth, flexShrink: 0, '& .MuiDrawer-paper': { width: leftDrawerWidth, boxSizing: 'border-box' } }} PaperProps={{ variant: 'outlined' }}> <div onMouseDown={() => handleLeftDrawerMouseDown()} className="left-dragger" /> </Drawer> <Main></Main> </React.Fragment> ); }
问题根源分析
- Styled组件在组件内部重复创建:每次
App组件因leftDrawerWidth变化重新渲染时,都会生成新的Mainstyled组件实例,旧实例无法被垃圾回收,导致FiberNode持续堆积。 - React调度器持有引用:
performWorkUntilDeadline是React调度器核心函数,负责处理异步任务。重复创建的组件实例对应的Fiber节点会被调度器上下文持有,无法被正常回收。
解决方案
1. 将Styled组件移到组件外部定义
避免每次渲染重复创建组件,通过props传递动态样式参数:
import React from 'react'; import './App.css'; import { styled } from '@mui/material/styles'; import { Transitions } from '@mui/material'; import { Drawer } from '@mui/material'; import { MUIStyledCommonProps } from '@mui/system'; export const shouldUseDevStreams = process.env.NODE_ENV == 'development'; // 移到组件外部,仅创建一次 const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'leftOpen' })(({ theme, marginLeft }: MUIStyledCommonProps & { marginLeft: number }) => ({ flexGrow: 1, transition: (theme?.transitions as Transitions).create('margin', { easing: (theme?.transitions as Transitions).easing.easeOut, duration: (theme?.transitions as Transitions).duration.enteringScreen }), marginLeft: `${marginLeft}px` })); export default function App() { const minDrawerWidth = 258; const maxDrawerWidth = 2000; const defaultDrawerWidth = 258; const [leftDrawerWidth, setLeftDrawerWidth] = React.useState(defaultDrawerWidth); const handleLeftDrawerMouseDown = () => { document.addEventListener('mouseup', handleLeftDrawerMouseUp, true); document.addEventListener('mousemove', handleLeftDrawerMouseMove, true); }; const handleLeftDrawerMouseUp = () => { document.removeEventListener('mouseup', handleLeftDrawerMouseUp, true); document.removeEventListener('mousemove', handleLeftDrawerMouseMove, true); }; const handleLeftDrawerMouseMove = (e: { clientX: number }) => { const newWidth = e.clientX - document.body.offsetLeft + 2; const maxWidth = Math.min(maxDrawerWidth, document.body.offsetWidth); if (newWidth > minDrawerWidth && newWidth < maxWidth) { setLeftDrawerWidth(newWidth); } }; return ( <React.Fragment> <Drawer data-testid="leftDrawer" variant="persistent" anchor="left" open={true} sx={{ width: leftDrawerWidth, flexShrink: 0, '& .MuiDrawer-paper': { width: leftDrawerWidth, boxSizing: 'border-box' } }} PaperProps={{ variant: 'outlined' }}> {/* 优化事件绑定,避免每次渲染创建新函数 */} <div onMouseDown={handleLeftDrawerMouseDown} className="left-dragger" /> </Drawer> {/* 通过props传递动态宽度 */} <Main marginLeft={leftDrawerWidth}></Main> </React.Fragment> ); }
2. 优化事件绑定(辅助优化)
原代码中onMouseDown={() => handleLeftDrawerMouseDown()}会每次渲染创建新函数,改为直接绑定函数可减少不必要的对象创建。
3. 验证修复效果
修改后拖拽Drawer调整宽度,内存增长会恢复正常,旧FiberNode会被垃圾回收机制正确清理。
内容的提问来源于stack exchange,提问作者Philippe Paulin
相关产品推荐
相关产品推荐

