React组件无法作为JSX组件使用的类型错误求助
错误信息
'ExportTemplateModal' cannot be used as a JSX component.
Its element type 'ReactElement<any, any> | Component<Omit<Subtract<IExportTemplateModalProps, WithTranslationProps>, keyof WithTranslation<...>> & WithTranslationProps, any, any> | null' is not a valid JSX element.
Type 'Component<Omit<Subtract<IExportTemplateModalProps, WithTranslationProps>, keyof WithTranslation<N, undefined>> & WithTranslationProps, any, any>' is not assignable to type 'Element | ElementClass | null'.
Type 'Component<Omit<Subtract<IExportTemplateModalProps, WithTranslationProps>, keyof WithTranslation<N, undefined>> & WithTranslationProps, any, any>' is not assignable to type 'ElementClass'.
The types returned by 'render()' are incompatible between these types.
Type 'ReactNode' is not assignable to type 'false | Element | null'.
Type 'undefined' is not assignable to type 'false | Element | null'.
涉及组件代码
export class ExportTemplateModal extends React.Component< IExportTemplateModalProps, IExportTemplateModalState > { private _subscriptionTimer: number = 0; constructor(props: IExportTemplateModalProps) { super(props); this.state = { ...initialState }; this._handleClose = this._handleClose.bind(this); this._handleExport = this._handleExport.bind(this); this._handleStatusUpdate = this._handleStatusUpdate.bind(this); } public async componentDidMount() { const { projectSuppliers } = this.props; const supplierMap = constructSupplierMap(projectSuppliers); const allPartnerIDs = projectSuppliers.map(supplier => supplier.supplierId).filter(Boolean); const partnerInfo = await loadPartnerInfo(allPartnerIDs); this.setState({ partnerInfo, supplierMap }); } public render() { const { open, projectSuppliers } = this.props; const { status } = this.state; const { Status } = status; const suppliers = addGenericSupplier(projectSuppliers); return ( <Modal bodySpacingInset="large" data-id="quote-export-template-modal-content" open={open} onClose={this._handleClose} title="Export template" scrollContainer="modal" width="35rem" > <div id="quote-export-template-modal-content"> {Status === 'NOT_STARTED' && this._renderSupplierSelection(suppliers)} {Status !== 'NOT_STARTED' && this._renderExportStatus()} </div> {Status === 'NOT_STARTED' && ( <ModalFooter> <Row alignmentVertical="center" alignmentHorizontal="right" widths="fit"> <Button className="mr3" type="secondary" onClick={this._handleClose}> Cancel </Button> <Button type="primary" onClick={this._handleExport}> Export </Button> </Row> </ModalFooter> )} </Modal> ); } } export default withTranslation()(ExportTemplateModal);
项目package.json依赖配置
"dependencies": { "@amzn/pb-central-gql-schema": "*", "@amzn/pb-central-ui-components": "^1.0.0", "@hookform/resolvers": "^1.3.7", "@react-icons/all-files": "^4.1.0", "@testing-library/dom": "^8.13.0", "@testing-library/react-hooks": "^8.0.1", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.7", "emotion": "^10.0.27", "is-hotkey": "^0.1.8", "is-url": "^1.2.4", "jest-environment-jsdom-sixteen": "^2.0.0", "lodash.clonedeep": "^4.5.0", "lodash.differenceby": "^4.8.0", "lodash.escaperegexp": "^4.1.2", "lodash.get": "^4.4.2", "lodash.isempty": "^4.4.0", "lodash.isequal": "^4.5.0", "lodash.set": "^4.3.2", "lodash.sortby": "^4.7.0", "lodash.uniqueid": "^4.0.1", "office-ui-fabric-react": "^7.107.0", "react-hook-form": "^6.15.7", "react-i18next": "^11.18.3", "react-redux": "^8.0.2", "react-router-dom": "^5.2.0", "slate": "^0.60.8", "slate-history": "^0.59.0", "slate-react": "^0.60.8" }, "peerDependencies": { "@amzn/meridian": "*", "@amzn/meridian-tokens": "*", "react": "^16.13.0", "react-dom": "^16.13.0", "date-fns": "^2.16.1" }, "devDependencies": { "@amzn/brazil": "*", "@amzn/meridian": "*", "@amzn/meridian-tokens": "*", "@babel/core": "^7.6.0", "@babel/helper-validator-identifier": "7.10.4", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", "@babel/plugin-proposal-object-rest-spread": "^7.12.1", "@babel/plugin-proposal-optional-chaining": "^7.12.1", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-react-jsx": "^7.12.1", "@babel/plugin-transform-runtime": "^7.12.1", "@babel/plugin-transform-typescript": "^7.12.1", "@babel/preset-env": "^7.12.1", "@babel/preset-react": "^7.12.1", "@babel/preset-typescript": "^7.12.1", "@babel/runtime": "7.4.5", "@sheerun/mutationobserver-shim": "^0.3.3", "@storybook/addon-actions": "^6.0.26", "@storybook/addon-essentials": "^6.0.26", "@storybook/addon-info": "5.3.21", "@storybook/addon-knobs": "^6.0.26", "@storybook/addon-links": "^6.0.26", "@storybook/addons": "^6.0.26", "@storybook/react": "^6.0.26", "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.2", "@types/date-fns": "^2.6.0", "@types/enzyme": "^3.10.7", "@types/history": "^4.7.8", "@types/is-url": "^1.2.4", "@types/jest": "^26.0.24", "@types/lodash.clonedeep": "^4.5.6", "@types/lodash.differenceby": "^4.8.6", "@types/lodash.get": "^4.4.6", "@types/lodash.isempty": "^4.4.6", "@types/lodash.isequal": "^4.5.5", "@types/lodash.set": "^4.3.6", "@types/lodash.sortby": "^4.7.6", "@types/react": "^16.9.23", "@types/react-dom": "^16.9.5", "@types/react-router-dom": "^5.2.0", "@types/storybook-react-router": "^1.0.1", "@typescript-eslint/eslint-plugin": "^4.4.0", "@typescript-eslint/parser": "^4.4.0", "babel-eslint": "^10.1.0", "babel-loader": "^8.1.0", "babel-plugin-module-resolver": "^3.1.1", "babel-plugin-transform-react-remove-prop-types": "^0.4.19", "date-fns": "^2.16.1", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.5", "eslint": "7.11.0", "eslint-config-prettier": "^7.0.0", "eslint-plugin-jest": "^24.1.0", "eslint-plugin-react": "^7.21.5", "file-loader": "^6.2.0", "fs-extra": "^5.0.0", "globby": "^8.0.1", "history": "^4.10.1", "husky": "^4.3.8", "identity-obj-proxy": "^3.0.0", "jest": "^24.9.0", "jest-environment-enzyme": "^7.1.2", "jest-enzyme": "^7.1.2", "jest-transform-stub": "^2.0.0", "jest-when": "^3.2.1", "lint-staged": "^10.5.3", "prettier": "^2.2.1", "prettier-plugin-organize-imports": "^1.1.1", "react-to-typescript-definitions": "^1.2.0", "storybook-react-router": "^1.0.8", "typescript": "^4.2.4", "webpack-cli": "^4.2.0" }, "npm-pretty-much": { "publishDir": "", "runRelease": "always" }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "src/**/*.{js,jsx,ts,tsx}": "eslint --cache --fix", "src/**/*.{js,ts,jsx,tsx,css,md}": "prettier --write" }
已尝试的无效方法
- 执行
npm i react@18 react-dom@18 @types/react@18 @types/react-dom@18更新版本 - 删除node_modules后重新执行npm install
- 调整导入方式
解决方案
1. 修复class组件render返回值类型
错误核心原因是React 16的class组件render方法不允许返回undefined,而你组件中调用的_renderSupplierSelection或_renderExportStatus方法可能在某些分支下返回了undefined。
检查这两个方法,确保它们在所有分支下都返回Element | false | null。例如:
private _renderSupplierSelection(suppliers: Supplier[]) { // 补充判断,避免返回undefined if (!suppliers || suppliers.length === 0) { return null; } // 原组件渲染逻辑 } private _renderExportStatus() { const { status } = this.state; // 确保所有分支都有明确返回值 if (status.Status === 'IN_PROGRESS') { return <div>Exporting...</div>; } if (status.Status === 'SUCCESS') { return <div>Export completed</div>; } // 默认返回null,避免undefined return null; }
2. 解决react-i18next与React版本的兼容性冲突
你升级到了React 18,但使用的react-i18next@11.18.3是适配React 16/17的版本,其类型定义与React 18不兼容。需要升级到支持React 18的版本:
npm install react-i18next@^12.2.0 i18next@^22.0.0 --save
同时确保@types/react和@types/react-dom的版本与安装的React版本完全一致,避免类型冲突。
3. 显式指定withTranslation泛型参数(可选)
如果升级后仍有类型问题,可显式指定withTranslation的泛型参数,明确组件的props类型:
export default withTranslation<IExportTemplateModalProps>()(ExportTemplateModal);
4. 回退React版本(临时方案)
若升级react-i18next触发其他依赖冲突,可回退到package.json中peerDependencies指定的React版本:
npm install react@^16.13.0 react-dom@^16.13.0 @types/react@^16.9.23 @types/react-dom@^16.9.5 --save
关于as any导致的测试错误
使用export default Component as any会丢失组件的类型信息,Enzyme无法识别它是class组件,因此调用state()时会抛出错误。上述方案均保留了组件的原始类型,可同时解决TS报错和测试问题。
内容的提问来源于stack exchange,提问作者Samira Arabgol

