如何将带泛型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
相关产品推荐
相关产品推荐

