React组件卸载时如何销毁内嵌的Flutter Web应用?
解决React组件卸载时Flutter Web应用未销毁的问题
你的核心问题是仅移除DOM元素和清空变量引用,没有调用Flutter Web引擎的销毁API,导致Flutter的隔离环境、事件监听和后台逻辑仍在运行。以下是具体修复方案:
关键问题分析
当前清理逻辑只做了:
- 移除DOM元素
- 清空
flutterState.current、engineInitializer、appRunner变量
但Flutter Web引擎实例仍在浏览器后台运行,包括: - Dart虚拟机的隔离环境未终止
- Flutter与React的通信监听未移除
- 引擎内部的定时器、状态更新逻辑仍在执行
修复步骤
1. 拆分引用存储
将flutterState拆分为两个ref,分别存储Flutter引擎实例和通信状态对象,避免混淆:
const flutterEngineRef = useRef<any>(null); // 存appRunner const flutterCommStateRef = useRef<any>(null); // 存CustomEvent传递的state
2. 在清理函数中调用引擎销毁API
Flutter Web的appRunner和engineInitializer提供了dispose()方法,用于销毁引擎实例:
// 清理函数中添加 if (flutterEngineRef.current) { // 销毁Flutter应用实例 await flutterEngineRef.current.dispose(); flutterEngineRef.current = null; } if (engineInitializer) { // 销毁引擎初始化器 engineInitializer.dispose(); engineInitializer = null; }
3. 移除Flutter到React的状态监听
你的代码中调用了state.listenToFlutterState,需要在清理时移除该监听(假设Flutter端提供了移除方法,比如removeStateListener):
if (flutterCommStateRef.current) { flutterCommStateRef.current.removeStateListener(); flutterCommStateRef.current = null; }
如果Flutter端没提供移除方法,需要在Dart代码中实现:
// Flutter端示例:添加移除监听的方法 void removeStateListener() { _stateStreamController.close(); }
4. 彻底清理DOM和关联资源
除了移除宿主元素,还要清理Flutter注入的全局资源:
// 移除Flutter生成的DOM节点 if (target) { target.innerHTML = ''; if (target.parentNode) { target.parentNode.removeChild(target); } } // 清理全局_flutter相关的临时资源(可选,视情况而定) if (window._flutter?.loader) { // 若有未完成的加载任务,取消它 window._flutter.loader.cancelLoading?.(); }
修改后的完整React组件清理逻辑
export const FlutterEmbedComponent: React.FC<> = memo(({ assetBase = '', src = 'main.dart.js', }) => { const flutterEngineRef = useRef<any>(null); const flutterCommStateRef = useRef<any>(null); const ref = useRef<HTMLDivElement>(null); const onFlutterAppLoaded = (state: any) => { flutterCommStateRef.current = state; // Update Flutter state once when loaded state.stateone(stateone); state.statetwo(statetwo); state.updateEnv(ENV); state.listenToFlutterState(state => { console.log(">>> state from flutter: ", state); }); }; useEffect(() => { const target = ref.current; let engineInitializer; const initFlutterApp = async () => { engineInitializer = await new Promise<any>((resolve) => { _flutter.loader.loadEntrypoint({ entrypointUrl: src, onEntrypointLoaded: resolve, }); }); console.log('initialize Flutter engine...'); const appRunner = await engineInitializer?.initializeEngine({ hostElement: target, assetBase: assetBase, }); console.log('run Flutter engine...'); await appRunner?.runApp(); flutterEngineRef.current = appRunner; }; initFlutterApp(); const eventListener = (event: Event) => { let state = (event as CustomEvent).detail; console.log('Flutter app initialized:', state); onFlutterAppLoaded(state); }; target?.addEventListener('flutter-initialized', eventListener, { once: true }); return async () => { // 移除事件监听 target?.removeEventListener('flutter-initialized', eventListener); // 移除Flutter到React的状态监听 if (flutterCommStateRef.current) { flutterCommStateRef.current.removeStateListener?.(); flutterCommStateRef.current = null; } // 销毁Flutter引擎实例 if (flutterEngineRef.current) { try { await flutterEngineRef.current.dispose(); } catch (e) { console.error('Failed to dispose Flutter engine:', e); } flutterEngineRef.current = null; } // 销毁引擎初始化器 if (engineInitializer) { try { engineInitializer.dispose(); } catch (e) { console.error('Failed to dispose engine initializer:', e); } engineInitializer = null; } // 清理DOM if (target) { target.innerHTML = ''; if (target.parentNode) { target.parentNode.removeChild(target); } } }; }, [assetBase, src]); // ... 其他useEffect和渲染逻辑不变 return ( <div ref={ref} style={{ height: '100%', width: '100%', display: showFlutterApp ? 'flex' : 'none', zIndex: 99, position: activePage === 2 ? 'fixed' : 'static', top: activePage === 2 ? 50 : 'auto', right: activePage === 2 ? 0 : 'auto', flex: 1 }} ></div> ); });
额外注意事项
- Flutter端配合:确保Dart代码中所有后台任务(定时器、流、WebSocket等)在引擎销毁时能自动终止,或者提供手动关闭的方法。
- Service Worker清理:如果你的Flutter应用注册了Service Worker,若不需要全局保留,可以在清理时注销:
if ('serviceWorker' in navigator) { const registrations = await navigator.serviceWorker.getRegistrations(); for (let reg of registrations) { if (reg.scope.includes('/flutter/')) { // 匹配你的Flutter资源路径 await reg.unregister(); } } } - 内存泄漏检测:可以用Chrome DevTools的Memory面板,对比组件卸载前后的内存占用,确认是否还有残留的Flutter相关对象。
内容的提问来源于stack exchange,提问作者user321553125
相关产品推荐
相关产品推荐

