配置了unmountOnExit,Modal 关闭后,仍未销毁DOM,如何排查?

131 阅读2分钟

以Arco Design为例,为什么配置了 unmountOnExit 但 Modal 仍未销毁 DOM。以下是详细的排查步骤:

1. 检查 Modal 的完整配置

import { Modal } from '@arco-design/web-react';

const YourComponent = () => {
  const [visible, setVisible] = useState(false);

  return (
    <Modal
      visible={visible}
      unmountOnExit={true}  // 明确设置为 true
      onCancel={() => {
        setVisible(false);
        // 确保 visible 状态被正确更新
        console.log('Modal closing, visible:', visible);
      }}
      afterClose={() => {
        // 添加 afterClose 回调来确认 Modal 是否正确关闭
        console.log('Modal closed');
      }}
      destroyOnClose={true}  // 某些组件库需要这个属性
    >
      {/* Modal content */}
    </Modal>
  );
};

2. 添加生命周期检查

const YourComponent = () => {
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    console.log('Modal visible state changed:', visible);
    
    return () => {
      console.log('Modal component cleanup');
    };
  }, [visible]);

  return (
    <Modal
      visible={visible}
      unmountOnExit
      // ... other props
    >
      <ModalContent />
    </Modal>
  );
};

const ModalContent = () => {
  useEffect(() => {
    console.log('ModalContent mounted');
    return () => {
      console.log('ModalContent unmounted');
    };
  }, []);

  return <div>Modal content</div>;
};

3. 检查父组件的渲染逻辑

const ParentComponent = () => {
  // 确保父组件的重渲染不会影响 Modal 的销毁
  const [parentState, setParentState] = useState(false);

  return (
    <div>
      <YourComponent key="modal" />  // 添加 key 确保组件正确重渲染
      {/* other content */}
    </div>
  );
};

4. 使用 React.memo 优化渲染

const ModalContent = React.memo(() => {
  // Modal 内容
  return <div>Modal content</div>;
});

const YourComponent = () => {
  const [visible, setVisible] = useState(false);
  
  const handleCancel = useCallback(() => {
    setVisible(false);
  }, []);

  return (
    <Modal
      visible={visible}
      unmountOnExit
      onCancel={handleCancel}
    >
      <ModalContent />
    </Modal>
  );
};

5. 检查是否有异步操作阻止销毁

const YourComponent = () => {
  const [visible, setVisible] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  const handleCancel = async () => {
    setIsLoading(true);
    try {
      // 确保异步操作完成后才关闭 Modal
      await someAsyncOperation();
      setVisible(false);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <Modal
      visible={visible}
      unmountOnExit
      onCancel={handleCancel}
      confirmLoading={isLoading}
    >
      {/* Modal content */}
    </Modal>
  );
};

6. 强制更新方案

const YourComponent = () => {
  const [visible, setVisible] = useState(false);
  const [key, setKey] = useState(0);

  const handleCancel = () => {
    setVisible(false);
    // 强制重新渲染 Modal
    setKey(prev => prev + 1);
  };

  return (
    <Modal
      key={key}
      visible={visible}
      unmountOnExit
      onCancel={handleCancel}
    >
      {/* Modal content */}
    </Modal>
  );
};

7. 使用 Portal 检查

import ReactDOM from 'react-dom';

const ModalPortal = ({ children, visible }) => {
  const [mounted, setMounted] = useState(false);
  const containerRef = useRef(null);

  useEffect(() => {
    if (visible && !mounted) {
      containerRef.current = document.createElement('div');
      document.body.appendChild(containerRef.current);
      setMounted(true);
    } else if (!visible && mounted) {
      containerRef.current?.remove();
      setMounted(false);
    }
  }, [visible, mounted]);

  if (!mounted) return null;

  return ReactDOM.createPortal(
    children,
    containerRef.current
  );
};

8. 检查样式覆盖

// 确保没有样式覆盖导致 Modal 看起来像被销毁
.arco-modal-wrapper {
  display: none !important; // 避免使用这样的样式覆盖
}

9. 添加调试代码

const YourComponent = () => {
  const modalRef = useRef(null);

  useEffect(() => {
    // 监控 Modal DOM 节点
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        console.log('DOM changed:', mutation);
      });
    });

    if (modalRef.current) {
      observer.observe(modalRef.current, {
        childList: true,
        subtree: true
      });
    }

    return () => observer.disconnect();
  }, []);

  return (
    <Modal
      ref={modalRef}
      // ... other props
    >
      {/* Modal content */}
    </Modal>
  );
};

总结

总结以上,比较隐晦的情况是异步请求或者异步数据阻止了组件销毁,尤其是存在轮训的情况下。