You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决涉及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>
  );
}

问题根源分析

  1. Styled组件在组件内部重复创建:每次App组件因leftDrawerWidth变化重新渲染时,都会生成新的Main styled组件实例,旧实例无法被垃圾回收,导致FiberNode持续堆积。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 17:31:00