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

react-tiny-popover组件TSDX构建遇TS2786错误:Popover无法作JSX组件

解决TS2786错误:Popover无法作为JSX组件使用

问题场景

项目基于tsdx、rollup和storybook搭建,使用react-tiny-popover库开发Popover组件,storybook运行正常,但执行tsdx构建时抛出TS2786类型错误。

错误信息

(typescript) Error: my-new-project/src/components/Popover/PopoverComponent.tsx(5,12): semantic error TS2786: 'Popover' cannot be used as a JSX component.
Its return type 'ReactNode' is not a valid JSX element.
Type 'string' is not assignable to type 'Element'.
Error: my-new-project/src/components/Popover/PopoverComponent.tsx(5,12): semantic error TS2786: 'Popover' cannot be used as a JSX component.
Its return type 'ReactNode' is not a valid JSX element.
Type 'string' is not assignable to type 'Element'.

组件代码

Popover主组件

import React, { useState } from 'react';
import { PopoverComponent } from './PopoverComponent';
import { Paper } from '../Paper';
import type { ReactNode } from 'react';

export type PopoverPosition = 'top' | 'bottom' | 'left' | 'right';

export interface PopoverProps {
  label: ReactNode | string;
  children: ReactNode | string;
  className?: string;
  classNamePanel?: string;
  containerClassName?: string;
  positions?: PopoverPosition;
  isCloseClickElement?: boolean;
}

export const Popover: React.FC<PopoverProps> = ({
  children,
  className = '',
  containerClassName = '',
  label,
  positions = 'bottom',
  isCloseClickElement,
  classNamePanel = '',
}) => {
  const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);

  return (
    <div className={`food2c-popover ${className}`}>
      <PopoverComponent
        containerStyle={{
          padding: '12px',
        }}
        positions={positions}
        isOpen={isPopoverOpen}
        reposition={false}
        onClickOutside={(e) => {
          e.stopPropagation();
          setIsPopoverOpen(false);
        }}
        containerClassName={containerClassName}
        content={() => (
          <div className={`food2c-popover-panel ${classNamePanel}`}>
            <Paper
              onClickPaper={() =>
                isCloseClickElement ? setIsPopoverOpen(false) : null
              }
            >
              {children}
            </Paper>
          </div>
        )}
      >
        <button
          className="food2c-popover-button"
          onClick={(e) => {
            e.stopPropagation();
            setIsPopoverOpen(!isPopoverOpen);
          }}
        >
          {label}
        </button>
      </PopoverComponent>
    </div>
  );
};

PopoverComponent封装

import React from 'react';
import { PopoverProps, Popover } from 'react-tiny-popover';

export const PopoverComponent: React.FC<PopoverProps> = props => {
  return (<Popover {...props}>{props.children}</Popover>) as JSX.Element;
};

项目配置文件

rollup.config.js

import sass from 'rollup-plugin-sass';
import replace from '@rollup/plugin-replace';

export default {
  input: 'index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'cjs',
  },
  plugins: [
    sass({
      insert: true,
      output: 'dist/bundle.css',
    }),
    replace({
      preventAssignment: true,
    }),
  ],
};

tsdx.config.js

const postcss = require('rollup-plugin-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const svgr = require('@svgr/rollup');

module.exports = {
  rollup(config, options) {
    config.plugins.push(
      postcss({
        plugins: [autoprefixer, cssnano],
        inject: false,
        extract: 'bundle.css',
      }),
      svgr()
    );

    return config;
  },
};

tsconfig.json

{
  // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
  "include": ["src", "types"],
  "compilerOptions": {
    "module": "esnext",
    "lib": ["dom", "esnext"],
    "importHelpers": true,
    "declaration": false,
    "sourceMap": true,
    "rootDir": "./src",
    "strict": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "moduleResolution": "node",
    "jsx": "react",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": false
  }
}

解决方案

核心问题分析

错误根源在于:

  1. PopoverComponent错误复用了react-tiny-popover的PopoverProps,该类型允许children为string,但React组件作为JSX使用时,返回值必须是合法JSX元素,string类型不符合要求。
  2. 手动添加as JSX.Element断言无法解决底层类型冲突,反而掩盖了类型不匹配的问题。

修复步骤

  1. 修正Props类型命名与匹配:将react-tiny-popover的PopoverProps重命名导入,避免与自定义组件的Props混淆,确保PopoverComponent的Props完全匹配第三方库要求。
  2. 移除无效类型断言:react-tiny-popover的Popover组件本身返回合法JSX元素,无需手动断言。
  3. 可选:约束父组件输入类型:如果需要严格控制输入,可将PopoverProps中的label和children限制为ReactElement类型,避免传入string。

修复后的PopoverComponent代码

import React from 'react';
import { PopoverProps as TinyPopoverProps, Popover as TinyPopover } from 'react-tiny-popover';

export const PopoverComponent: React.FC<TinyPopoverProps> = props => {
  return <TinyPopover {...props}>{props.children}</TinyPopover>;
};

可选:严格约束父组件Props

export interface PopoverProps {
  label: React.ReactElement;
  children: React.ReactElement;
  className?: string;
  classNamePanel?: string;
  containerClassName?: string;
  positions?: PopoverPosition;
  isCloseClickElement?: boolean;
}

验证修复

执行tsdx build命令,此时类型检查将通过,构建可正常完成。


内容的提问来源于stack exchange,提问作者William Gabriel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 20:33:14