如何让自定义React If组件支持TypeScript类型收窄?
你的问题核心在于:原有的If组件仅接收布尔值作为condition,且直接传入ReactNode作为子元素——TypeScript无法建立condition为true与子元素中props.alert已被收窄之间的关联,因为ReactNode是静态分析的,不管condition是否成立,TypeScript都会校验所有代码分支。
要解决这个问题,需要把If组件改成接收回调函数作为子元素,通过泛型关联条件判断结果与回调参数的类型,让TypeScript能在回调内部自动完成类型收窄。
方案1:重构If组件为回调式(通用版)
修改If组件的定义,让它接收一个可能为null/undefined的值作为判断条件,子元素是仅当该值存在时才执行的回调:
import React from 'react'; interface IfProps<T> { // 要判断的目标值,允许为null/undefined when: T | null | undefined; // 回调函数,参数为非空的目标值 children: (value: NonNullable<T>) => React.ReactNode; } export default function If<T>({ when, children }: IfProps<T>) { // 仅当when存在时,执行回调并传入非空值 return when != null ? children(when) : null; }
使用方式
在Test组件中,直接传入props.alert作为when参数,回调函数的参数会自动被收窄为Alert类型:
interface Alert { message: { title: string; content: string; } } interface P { alert?: Alert; } function Test(props: P) { return ( <div> <span>Lorem Ipsum</span> <If when={props.alert}> {/* alert自动被收窄为Alert类型,无类型错误 */} {(alert) => ( <div> <span>{alert.message.title}</span> </div> )} </If> </div> ); }
方案2:结合自定义类型谓词函数
如果你一定要保留hasAlert这样的类型谓词函数,可以调整If组件的泛型定义,让它关联布尔条件与收窄后的类型:
import React from 'react'; interface IfProps<C extends boolean, T> { condition: C; // 仅当condition为true时,要求传入接收T类型参数的回调 children: C extends true ? (value: T) => React.ReactNode : never; // 传入需要被收窄的目标值 value: T | null | undefined; } export default function If<C extends boolean, T>(props: IfProps<C, T>) { if (props.condition) { return props.children(props.value as T); } return null; }
使用方式
传入hasAlert的结果作为condition,同时传入props.alert作为value,回调中自动拿到收窄后的Alert类型:
function hasAlert(alert?: Alert): alert is Alert { return !!alert; // 替换为你的isEmpty逻辑 } function Test(props: P) { return ( <div> <span>Lorem Ipsum</span> <If condition={hasAlert(props.alert)} value={props.alert} > {(alert) => ( <div> <span>{alert.message.title}</span> </div> )} </If> </div> ); }
为什么原方案不生效?
原If组件的children是ReactNode,TypeScript会提前解析所有子元素的代码,即使condition为false,它也会检查props.alert.message.title的合法性——而此时props.alert可能是undefined,因此报错。改用回调后,只有当条件成立时才会执行回调,TypeScript能识别到这一点,从而在回调内部安全地进行类型收窄。
内容的提问来源于stack exchange,提问作者RexiaN

