TypeScript条件泛型下如何让IntelliSense正常工作?
问题背景
现有如下Button组件的TypeScript类型定义,在实例化组件时,variantProps能根据variant的值获得正确的类型提示,但在组件内部通过variant判断逻辑访问variantProps时,IntelliSense无法提供对应属性的提示:
export type ButtonVariant = "solid" | "light" | "outline" | "ghost" | "link" | "highlight"; type OutlineVariantProps = { backgroundColor?: string }; type SolidGhostVariantProps = { isHighlightOnActive?: boolean }; export type VariantProps<T extends ButtonVariant> = T extends "outline" ? OutlineVariantProps : T extends "ghost" | "solid" ? SolidGhostVariantProps : never; export type ButtonProps<T extends ButtonVariant = "solid"> = { variant?: T; variantProps?: VariantProps<T>; } & React.ButtonHTMLAttributes<HTMLButtonElement>;
比如在组件内部写以下代码时,variantProps不会自动提示对应属性:
if(variant === "outline" && variantProps.[...])
原因分析
原类型定义使用泛型T关联variant和variantProps,但这种关联仅在组件实例化、TypeScript推断出具体的T值时生效。在组件内部处理props时,泛型T是单一固定类型(比如默认的solid,或者用户传入的某个具体variant),无法覆盖所有可能的variant联合类型场景,导致TypeScript无法通过variant的判断逻辑自动窄化variantProps的类型。
解决方案
方案1:重构为可辨识联合类型(推荐)
将ButtonProps重构为可辨识联合类型,明确每个variant值对应的variantProps类型,让TypeScript能自动通过variant的值窄化variantProps的类型:
import React from 'react'; export type ButtonVariant = "solid" | "light" | "outline" | "ghost" | "link" | "highlight"; type OutlineVariantProps = { backgroundColor?: string }; type SolidGhostVariantProps = { isHighlightOnActive?: boolean }; // 定义每个variant对应的专属props结构 type ButtonVariantSpecificProps = | { variant: "outline"; variantProps?: OutlineVariantProps } | { variant: "solid"; variantProps?: SolidGhostVariantProps } | { variant: "ghost"; variantProps?: SolidGhostVariantProps } | { variant: "light" | "link" | "highlight"; variantProps?: never }; // 按钮基础属性 type ButtonBaseProps = React.ButtonHTMLAttributes<HTMLButtonElement>; // 最终ButtonProps为基础属性 + 各variant专属属性的联合类型 export type ButtonProps = ButtonBaseProps & ButtonVariantSpecificProps; // 组件实现示例 const Button = (props: ButtonProps) => { const { variant = "solid", variantProps, ...rest } = props; if (variant === "outline") { // IntelliSense会自动提示backgroundColor属性 console.log(variantProps?.backgroundColor); } else if (variant === "solid" || variant === "ghost") { // IntelliSense会自动提示isHighlightOnActive属性 console.log(variantProps?.isHighlightOnActive); } return <button {...rest} />; };
这种方式直接把variant和variantProps的关联固化在类型结构中,TypeScript无需额外辅助就能完成类型窄化,代码更直观,类型提示也更准确。
方案2:自定义类型守卫函数
如果不想改动原有Props结构,可以通过自定义类型守卫函数,手动告诉TypeScript当variant为特定值时,variantProps对应的类型:
import React from 'react'; export type ButtonVariant = "solid" | "light" | "outline" | "ghost" | "link" | "highlight"; type OutlineVariantProps = { backgroundColor?: string }; type SolidGhostVariantProps = { isHighlightOnActive?: boolean }; export type VariantProps<T extends ButtonVariant> = T extends "outline" ? OutlineVariantProps : T extends "ghost" | "solid" ? SolidGhostVariantProps : never; export type ButtonProps<T extends ButtonVariant = "solid"> = { variant?: T; variantProps?: VariantProps<T>; } & React.ButtonHTMLAttributes<HTMLButtonElement>; // 类型守卫:判断props是否为outline类型 function isOutlineButtonProps(props: ButtonProps): props is ButtonProps<"outline"> { return props.variant === "outline"; } // 类型守卫:判断props是否为solid/ghost类型 function isSolidGhostButtonProps(props: ButtonProps): props is ButtonProps<"solid"> | ButtonProps<"ghost"> { return props.variant === "solid" || props.variant === "ghost"; } // 组件实现示例 const Button = <T extends ButtonVariant = "solid">(props: ButtonProps<T>) => { const { variant = "solid", ...rest } = props; if (isOutlineButtonProps(props)) { // 自动提示backgroundColor属性 console.log(props.variantProps?.backgroundColor); } else if (isSolidGhostButtonProps(props)) { // 自动提示isHighlightOnActive属性 console.log(props.variantProps?.isHighlightOnActive); } return <button {...rest} />; };
类型守卫函数通过返回布尔值,向TypeScript断言当前props的具体类型,从而让IntelliSense能正确识别variantProps的属性。
内容的提问来源于stack exchange,提问作者benoit rastier

