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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 21:51:48