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

如何将带泛型Props的Modal组件作为可选Prop传入SelectBase并解决类型错误?

问题解决与优化方案

核心问题分析

类型错误的根源是:

  • 泛型ModalProps没有约束必须包含children以及组件运行时需要的open、onClose等基础属性
  • 默认组件BaseMenuList和Drawer的Props类型与自定义ModalProps未做兼容处理

具体修复步骤

1. 定义基础模态Props约束

先抽离所有模态组件都需要的核心属性,让泛型ModalProps必须继承这些属性,同时强制支持children:

import { PopoverProps } from '@mui/material';
import { ReactNode } from 'react';

// 所有模态组件必须具备的核心属性
type BaseModalCoreProps = {
  open: boolean;
  onClose: PopoverProps['onClose'];
  anchorEl?: HTMLElement | null;
};

// 约束ModalProps必须包含核心属性和children
interface ISelectBasePropss<ModalProps extends BaseModalCoreProps & React.PropsWithChildren<unknown>> {
  inputRenderMode: InputRenderMode;
  modalRenderMode: 'desktop' | 'mobile';
  open: boolean;
  onClick: () => void;
  onClose: PopoverProps['onClose'];
  type?: 'select' | 'input';
  ModalComponent?: ComponentType<ModalProps>;
  InputComponent?: ComponentType<IInputBaseProps>;
  ModalComponentProps?: Omit<ModalProps, keyof BaseModalCoreProps>; // 剔除已由组件内部注入的核心属性
  InputComponentProps?: IInputBaseProps;
  children: ReactNode;
}

2. 修正modalProps的类型赋值

组件内部注入核心属性时,和传入的ModalComponentProps合并,避免类型冲突:

const modalProps: ModalProps = {
  ...(ModalComponentProps as Omit<ModalProps, keyof BaseModalCoreProps>),
  open,
  anchorEl: selectRef.current,
  onClose,
} as ModalProps;

3. 处理默认组件的类型兼容

给默认组件的联合类型做类型断言,确保和泛型ModalProps兼容:

const BaseModalComponent = (modalRenderMode === 'desktop' ? BaseMenuList : Drawer) as ComponentType<ModalProps>;
const Modal = ModalComponent ?? BaseModalComponent;

更优实现方案

1. 抽离基础模态类型

把模态相关的类型抽成单独文件,方便整个项目复用,同时统一默认组件的Props处理:

// types/modal.ts
import { PopoverProps } from '@mui/material';
export type BaseModalCoreProps = {
  open: boolean;
  onClose: PopoverProps['onClose'];
  anchorEl?: HTMLElement | null;
};
export type ModalComponentType<Props extends BaseModalCoreProps> = ComponentType<Props & React.PropsWithChildren<unknown>>;

2. 简化组件泛型使用

让组件的泛型默认值指向默认组件的Props,这样用户不传入自定义ModalComponent时不需要手动指定泛型:

import { BaseModalCoreProps, ModalComponentType } from './types/modal';
import { DrawerProps, MenuListProps } from '@mui/material';

// 默认模态Props类型:桌面端用MenuListProps,移动端用DrawerProps
type DefaultModalProps = (MenuListProps | DrawerProps) & BaseModalCoreProps;

const SelectBase = <ModalProps extends BaseModalCoreProps = DefaultModalProps>({
  // ... props定义
}: ISelectBasePropss<ModalProps>) => {
  // ... 组件逻辑不变
};

3. 优化Props传递逻辑

用Omit剔除组件内部已注入的属性,避免用户重复传入导致冲突,同时增强类型安全性。


完整修复后的核心代码片段

import { ComponentType, ReactNode, useRef } from 'react';
import { Drawer, PopoverProps, MenuListProps } from '@mui/material';
import { BaseMenuList } from 'portfolio3/ui-kit';
import { IInputBaseProps, StyledSelectBase } from 'portfolio3/ui-kit/selects/bases';
import { InputRenderMode } from 'portfolio3/ui-kit/types';

// 核心模态属性约束
type BaseModalCoreProps = {
  open: boolean;
  onClose: PopoverProps['onClose'];
  anchorEl?: HTMLElement | null;
};

// 默认模态Props类型
type DefaultModalProps = (MenuListProps | DrawerProps) & BaseModalCoreProps;

interface ISelectBasePropss<ModalProps extends BaseModalCoreProps = DefaultModalProps> {
  inputRenderMode: InputRenderMode;
  modalRenderMode: 'desktop' | 'mobile';
  open: boolean;
  onClick: () => void;
  onClose: PopoverProps['onClose'];
  type?: 'select' | 'input';
  ModalComponent?: ComponentType<ModalProps & React.PropsWithChildren<unknown>>;
  InputComponent?: ComponentType<IInputBaseProps>;
  ModalComponentProps?: Omit<ModalProps, keyof BaseModalCoreProps>;
  InputComponentProps?: IInputBaseProps;
  children: ReactNode;
}

const SelectBase = <ModalProps extends BaseModalCoreProps = DefaultModalProps>({
  inputRenderMode,
  modalRenderMode,
  open,
  onClick,
  onClose,
  InputComponent,
  ModalComponent,
  ModalComponentProps,
  InputComponentProps,
  children,
  type = 'select',
}: ISelectBasePropss<ModalProps>) => {
  const selectRef = useRef<HTMLDivElement>(null);

  const inputProps: IInputBaseProps = {
    ...InputComponentProps,
    rendermode: inputRenderMode,
    ref: selectRef,
    onClick,
    readOnly: type === 'select',
    inputProps: {
      style: {
        cursor: type === 'select' ? 'pointer' : 'initial',
      },
    },
  };

  const modalProps: ModalProps = {
    ...(ModalComponentProps as Omit<ModalProps, keyof BaseModalCoreProps>),
    open,
    anchorEl: selectRef.current,
    onClose,
  } as ModalProps;

  const inputElement = InputComponent ? <InputComponent {...inputProps} /> : <StyledSelectBase {...inputProps} />;

  const BaseModalComponent = (modalRenderMode === 'desktop' ? BaseMenuList : Drawer) as ComponentType<ModalProps & React.PropsWithChildren<unknown>>;
  const Modal = ModalComponent ?? BaseModalComponent;

  const modalElement = <Modal {...modalProps}>{children}</Modal>;

  return (
    <div className="select-base">
      {inputElement}
      {modalElement}
    </div>
  );
};

export default SelectBase;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 01:30:50