TypeScript联合类型组件传参问题:布尔值限制与必传属性报错
受控/非受控组件的TypeScript类型兼容问题解决
问题
- 传递
boolean类型的isControlledManually时,TypeScript报错“type 'boolean' is not assignable to type 'false'”,强制要求传入字面量true或false - 传入
false调试时,仍提示formControl、fieldPath、selectedNonRhfValue不能为undefined
原因分析
- 问题1:
SelectField的props是Manual | NotManual判别式联合类型,要求isControlledManually必须是字面量true/false,普通boolean类型是两者的联合,无法直接匹配到具体分支 - 问题2:
QueryField中直接使用Omit<Manual | NotManual, ...>会把联合类型转为交叉类型,导致原本分支中必选的属性变成可选,丢失了类型判别能力
解决方案
1. 保持联合类型结构,拆分QueryField的props类型
不要直接对联合类型使用Omit,而是分别对Manual和NotManual做Omit后再组成联合类型,保留判别式:
// 拆分QueryField的props类型 export type QueryFieldManualProps<FormState extends FieldValues, ItemType, ItemValue extends string | number> = Omit< Manual<FormState, ItemType, ItemValue>, 'data' | 'isLoading' | 'isLoadingError' > & { queryResult: QueryResult<ItemType[]>; dataFilter?: (item: ItemType) => boolean; }; export type QueryFieldNotManualProps<FormState extends FieldValues, ItemType, ItemValue extends string | number> = Omit< NotManual<FormState, ItemType, ItemValue>, 'data' | 'isLoading' | 'isLoadingError' > & { queryResult: QueryResult<ItemType[]>; dataFilter?: (item: ItemType) => boolean; }; // 修改QueryField的定义 export function QueryField<FormState extends FieldValues, ItemType, ItemValue extends string | number>( props: QueryFieldManualProps<FormState, ItemType, ItemValue> | QueryFieldNotManualProps<FormState, ItemType, ItemValue> ) { const getData = () => props.queryResult.data || []; const getFilteredData = () => (props.dataFilter ? getData().filter(props.dataFilter) : getData()); return ( <SelectField {...props} data={getFilteredData()} isLoading={props.queryResult.isLoading} isLoadingError={props.queryResult.isError} /> ); }
2. 支持动态boolean值(可选)
如果需要允许调用方传入动态的boolean变量(而非字面量),可以在QueryField内部添加类型守卫,帮助TypeScript推断分支类型:
// 类型守卫函数 function isManual<FormState extends FieldValues, ItemType, ItemValue extends string | number>( props: QueryFieldManualProps<FormState, ItemType, ItemValue> | QueryFieldNotManualProps<FormState, ItemType, ItemValue> ): props is QueryFieldManualProps<FormState, ItemType, ItemValue> { return props.isControlledManually === true; } // 在QueryField中使用类型守卫 export function QueryField<FormState extends FieldValues, ItemType, ItemValue extends string | number>( props: QueryFieldManualProps<FormState, ItemType, ItemValue> | QueryFieldNotManualProps<FormState, ItemType, ItemValue> ) { const getData = () => props.queryResult.data || []; const getFilteredData = () => (props.dataFilter ? getData().filter(props.dataFilter) : getData()); // 类型守卫确保TypeScript正确推断props类型 if (isManual(props)) { return ( <SelectField {...props} data={getFilteredData()} isLoading={props.queryResult.isLoading} isLoadingError={props.queryResult.isError} /> ); } return ( <SelectField {...props} data={getFilteredData()} isLoading={props.queryResult.isLoading} isLoadingError={props.queryResult.isError} /> ); }
效果
修改后:
- 可以正常传递字面量
true/false,也支持动态boolean变量(配合类型守卫) - TypeScript会根据
isControlledManually的值,正确校验必选属性:- 当
isControlledManually: true时,强制要求传入selectedNonRhfValue,formControl和fieldPath可选 - 当
isControlledManually: false时,强制要求传入formControl和fieldPath,selectedNonRhfValue可选
- 当
内容的提问来源于stack exchange,提问作者Romasik
相关产品推荐
相关产品推荐

