React组件条件类型Props不生效,判别式联合正常,求排查
泛型条件类型无法通过判别式收窄的原因与解决方案
你的问题核心是:泛型条件类型ActionableProps<T>虽然能根据泛型参数T生成对应类型,但在组件函数内部,通过actionableType的值判断无法自动收窄actionableProps的类型,而改用判别式联合类型却能正常工作。这不是React上下文的问题,而是TypeScript泛型系统的特性限制。
为什么泛型条件类型无法收窄
泛型参数T在组件函数内部是一个未具体化的类型变量——TypeScript无法在运行时追踪泛型的具体类型,因此即使你检查了actionableType === "iconButton",也无法反向推导出泛型参数T的具体值,自然也就不能将actionableProps的类型从ActionableProps<T>收窄为ActionableProps<"iconButton">。
而判别式联合类型是具体的类型集合,TypeScript可以直接通过判别式字段(比如actionableType)的值,精准匹配到对应的联合成员,从而完成类型收窄。
解决方案
方案1:改用判别式联合类型(推荐)
放弃泛型,直接定义包含判别式的联合类型,让TypeScript自动完成类型收窄:
import React, { useState, useRef } from "react"; import { Button, ButtonProps, IconButton, Menu } from "@neo4j-ndl/react"; import type { IconButtonProps, ContextMenuProps } from "@neo4j-ndl/react"; type ActionableWithMenuProps = ( | { actionableType: "button"; actionableProps: { "data-testid": string; } & Omit<ButtonProps, "onClick" | "ref" | "children"> & { zzButtonText: ButtonProps["children"]; }; } | { actionableType: "iconButton"; actionableProps: { "data-testid": string; } & Omit<IconButtonProps, "onClick" | "ref" | "children"> & { zzIcon: IconButtonProps["children"]; }; } ) & { menuProps: Omit< ContextMenuProps, "open" | "onClose" | "anchorEl" | "ref" | "children" >; children: ContextMenuProps["children"]; }; const ConditionalComponent = ({ actionableType, actionableProps, menuProps, children, }: ActionableWithMenuProps) => { const [isMenuOpen, setIsMenuOpen] = useState(false); const menuAnchorRef = useRef(null); let actionable: JSX.Element; if (actionableType === "iconButton") { const { zzIcon, ...rest } = actionableProps; // 类型收窄正常 actionable = <IconButton {...rest}>{zzIcon}</IconButton>; } else { const { zzButtonText, ...rest } = actionableProps; // 类型收窄正常 actionable = <Button {...rest}>{zzButtonText}</Button>; } return ( <> {actionable} <Menu open={isMenuOpen && !!menuAnchorRef.current} onClose={() => setIsMenuOpen(false)} anchorEl={menuAnchorRef.current} > {children} </Menu> </> ); };
方案2:使用类型守卫手动收窄
如果需要保留泛型,可以自定义类型守卫,手动告诉TypeScript当前actionableProps的类型:
import React, { useState, useRef } from "react"; import { Button, ButtonProps, IconButton, Menu } from "@neo4j-ndl/react"; import type { IconButtonProps, ContextMenuProps } from "@neo4j-ndl/react"; type ActionableType = "button" | "iconButton"; type ActionableProps<T extends ActionableType> = { "data-testid": string; } & (T extends "button" ? Omit<ButtonProps, "onClick" | "ref" | "children"> & { zzButtonText: ButtonProps["children"]; } : Omit<IconButtonProps, "onClick" | "ref" | "children"> & { zzIcon: IconButtonProps["children"]; }); type ConditionalActionableProps<T extends ActionableType> = { actionableType: T; actionableProps: ActionableProps<T>; }; type ActionableWithMenuProps<T extends ActionableType> = ConditionalActionableProps<T> & { menuProps: Omit< ContextMenuProps, "open" | "onClose" | "anchorEl" | "ref" | "children" >; children: ContextMenuProps["children"]; }; // 自定义类型守卫 function isIconButtonProps<T extends ActionableType>( type: T, props: ActionableProps<T> ): props is ActionableProps<"iconButton"> { return type === "iconButton"; } const ConditionalComponent = <T extends ActionableType = "iconButton">({ actionableType, actionableProps, menuProps, children, }: ActionableWithMenuProps<T>) => { const [isMenuOpen, setIsMenuOpen] = useState(false); const menuAnchorRef = useRef(null); let actionable: JSX.Element; if (isIconButtonProps(actionableType, actionableProps)) { const { zzIcon, ...rest } = actionableProps; actionable = <IconButton {...rest}>{zzIcon}</IconButton>; } else { const { zzButtonText, ...rest } = actionableProps as ActionableProps<"button">; actionable = <Button {...rest}>{zzButtonText}</Button>; } return ( <> {actionable} <Menu open={isMenuOpen && !!menuAnchorRef.current} onClose={() => setIsMenuOpen(false)} anchorEl={menuAnchorRef.current} > {children} </Menu> </> ); };
内容的提问来源于stack exchange,提问作者konsalex
相关产品推荐
相关产品推荐

