React 18升级后LexicalComposer卸载时报destroy不是函数问题排查
TypeError: destroy is not a function问题排查与修复 问题描述
我开发的应用导入了一个私有NPM包,其中包含基于LexicalComposer实现的文本编辑器组件。将应用升级至React 18版本后,在Lexical编辑器组件卸载时遇到错误:TypeError: destroy is not a function。经控制台排查,怀疑错误源自Lexical的LexicalComposer实现。
已知React 18中这类错误通常和useEffect钩子的异步操作错误用法有关,想明确:
- Lexical是否支持React 18?
- 该错误的成因是什么?
- 如何修复?
编辑器实现代码
import React from 'react'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import { StandardPlugins } from '@myapp/myapp-lexical-react'; import styles from './myappLexicalEditor.module.scss'; import classNames from 'classnames/bind'; import { myappLexicalEditorTheme } from './myappLexicalEditorTheme'; import { MyappLexicalEditorProps } from './myappLexicalEditor.types'; const cx = classNames.bind(styles); /** * My App implementation of the Lexical Rich Text Editor */ const LexicalEditor = ({ editorConfig, children, toolbars, readOnly, onEditorChange, editorClass, editorStyle, editorContainerClass, editorContainerStyle, editorChildren, toolbarClass, placeholder, plugins, disableHistoryPlugin, }: MyappLexicalEditorProps) => { const Placeholder = placeholder || null; return ( <LexicalComposer initialConfig={{ ...editorConfig, theme: { ...myappLexicalEditorTheme, ...editorConfig.theme }, editable: !readOnly }}> <div className={toolbarClass}> <> {Array.isArray(toolbars) && toolbars.map((toolbar, i) => { const { ToolbarComponent, props, showToolbar } = toolbar; const finalShowToolbar = showToolbar !== undefined ? showToolbar : !readOnly; return finalShowToolbar ? <ToolbarComponent key={i} {...props} /> : ''; }) } </> </div> <div> <div> <div style={editorStyle} > <RichTextPlugin contentEditable={<ContentEditable />} ErrorBoundary={LexicalErrorBoundary} placeholder={!readOnly ? Placeholder : null} /> {editorChildren} </div> </div> </div> <StandardPlugins onChange={onEditorChange} disableHistory={Boolean(disableHistoryPlugin)}/> <> {plugins && plugins.map((pluginObj, i) => { const { PluginComponent, props } = pluginObj; return <PluginComponent key={i} {...props} />; })} </> {children} </LexicalComposer> ); }; export default LexicalEditor;
错误追踪信息
Uncaught TypeError: destroy is not a function at safelyCallDestroy (react-dom.development.js:22928:5) at commitHookEffectListUnmount (react-dom.development.js:23096:11) at commitPassiveUnmountInsideDeletedTreeOnFiber (react-dom.development.js:25094:11) at commitPassiveUnmountEffectsInsideOfDeletedTree_begin (react-dom.development.js:25044:5) at commitPassiveUnmountEffects_begin (react-dom.development.js:24952:11) at commitPassiveUnmountEffects (react-dom.development.js:24937:3) at flushPassiveEffectsImpl (react-dom.development.js:27034:3) at flushPassiveEffects (react-dom.development.js:26980:14) at commitRootImpl (react-dom.development.js:26931:5) at commitRoot (react-dom.development.js:26678:5) The above error occurred in the <observerComponent> component: at observerComponent (webpack-internal:///./node_modules/mobx-react-lite/es/observer.js:51:73) at div at observerComponent (webpack-internal:///./node_modules/mobx-react-lite/es/observer.js:51:73) at _class (webpack-internal:///./frontend/js/stores/index.js:163:88) at I18nextWithTranslation (webpack-internal:///./node_modules/react-i18next/dist/es/withTranslation.js:28:31) at div at LexicalComposer (webpack-internal:///./node_modules/@lexical/react/LexicalComposer.dev.js:43:3) at w (webpack-internal:///./node_modules/@myapp/myapp-lexical-react/lib/components/MyappLexicalEditor/MyappLexicalEditor.es.js:29:172) at div at div at Summary (webpack-internal:///./frontend/js/components/reusableComponents/Deal/ResponseDetails/components/SummarySection/Summary/summary.js:41:25) at div at SummarySection (webpack-internal:///./frontend/js/components/reusableComponents/Deal/ResponseDetails/components/SummarySection/summarySection.js:23:32) at div at observerComponent (webpack-internal:///./node_modules/mobx-react-lite/es/observer.js:51:73) at _class (webpack-internal:///./frontend/js/stores/index.js:163:88) at I18nextWithTranslation (webpack-internal:///./node_modules/react-i18next/dist/es/withTranslation.js:28:31) at div at TabPane (webpack-internal:///./node_modules/rc-tabs/es/TabPanelList/TabPane.js:16:24) at div at div at TabPanelList (webpack-internal:///./node_modules/rc-tabs/es/TabPanelList/index.js:16:17) at div at Tabs (webpack-internal:///./node_modules/rc-tabs/es/Tabs.js:73:17) at Tabs (webpack-internal:///./node_modules/antd/es/tabs/index.js:45:17) at div at observerComponent (webpack-internal:///./node_modules/mobx-react-lite/es/observer.js:51:73) at I18nextWithTranslation (webpack-internal:///./node_modules/react-i18next/dist/es/withTranslation.js:28:31) at _class (webpack-internal:///./frontend/js/stores/index.js:163:88) at I18nextWithTranslation (webpack-internal:///./node_modules/react-i18next/dist/es/withTranslation.js:28:31) at Route (webpack-internal:///./node_modules/react-router/esm/react-router.js:648:29) at Switch (webpack-internal:///./node_modules/react-router/esm/react-router.js:850:29) at div at Container (webpack-internal:///./node_modules/@myapp/myapp-ui/components/container/Container.js:18:22) at observerComponent (webpack-internal:///./node_modules/mobx-react-lite/es/observer.js:51:73) at _class (webpack-internal:///./frontend/js/stores/index.js:163:88) at I18nextWithTranslation (webpack-internal:///./node_modules/react-i18next/dist/es/withTranslation.js:28:31)
已尝试方案:用错误边界包裹组件无效;在主应用中添加useRef并在useEffect卸载时赋值为null,未解决问题。
解答
1. Lexical对React 18的支持
Lexical从v0.3.0版本开始正式支持React 18,但v0.3.0之前的旧版本在React 18的严格模式或卸载流程中会出现兼容问题,比如你遇到的destroy is not a function错误。
2. 错误成因
这个错误的核心原因:
- 旧版Lexical内部部分
useEffect钩子返回了非函数类型的销毁逻辑(比如null),或直接返回undefined之外的无效值。React 18会严格校验useEffect的返回值——若返回值存在,必须是可调用的销毁函数,否则就会抛出该错误。 - 私有包
@myapp/myapp-lexical-react中的StandardPlugins或自定义插件也可能存在类似问题,比如插件的useEffect错误处理了销毁逻辑。
3. 修复方案
方案一:升级Lexical到兼容React 18的版本
将项目中@lexical/react和lexical核心包升级到v0.3.0及以上:
npm install lexical@^0.3.0 @lexical/react@^0.3.0
同时检查私有包@myapp/myapp-lexical-react的依赖,同步升级其内部的Lexical版本,确保依赖链完全兼容React 18。
方案二:修复自定义插件/StandardPlugins中的useEffect问题
若升级Lexical后问题仍存在,检查StandardPlugins或自定义插件的实现,确保所有useEffect的返回值要么是一个销毁函数,要么不返回任何值(即默认返回undefined)。
错误示例:
useEffect(() => { // 初始化逻辑 return null; // React 18不允许返回非函数/undefined的值 });
正确示例:
// 无需销毁逻辑时,不返回任何值 useEffect(() => { // 初始化逻辑 }); // 需要销毁逻辑时,返回函数 useEffect(() => { const subscription = editor.registerListener(...); return () => { subscription.unsubscribe(); }; }, [editor]);
方案三:临时禁用React 18严格模式(仅临时调试)
若暂时无法升级依赖,可在React 18入口文件中移除<StrictMode>,绕过React的双重挂载/卸载校验逻辑。但这只是临时方案,长期仍需升级依赖解决根本问题。
内容的提问来源于stack exchange,提问作者Con

