调用React Hook时用TypeScript动态约束filter字段类型的需求
问题描述
我正在调用这个React Hook:
const count = useDocsCount({ collectionRef: 'notifications', filter: { filterKey: 'seen', operator: '==', filterValue: false } })
其中filter对象包含三个字段。我需要确保filterKey是INotification接口里的字段,这个接口定义是:
export interface INotification { dateCreated: Timestamp, seen: boolean, notificationID: string, text: string, title: string, type: TNotificationType }
我的需求有三个:
- 要是
filterKey不属于目标接口的字段,TypeScript得报错; - 不想在Hook内部硬编码这个约束,因为其他组件用这个Hook时依赖的接口不一样;
- 最好能做到:当
filterKey选seen这类具体字段时,filterValue的类型和接口里对应字段的类型(比如seen是boolean)完全匹配。
解决方案
用TypeScript的泛型+索引类型就能实现这种动态约束,让Hook适配任意目标接口,同时自动关联字段键和值的类型。
1. 改造Hook的类型定义
把useDocsCount改成泛型函数,接收一个泛型参数T代表目标接口,然后通过索引类型约束filter的结构:
import { Timestamp } from 'firebase/firestore'; // 先定义支持的操作符类型(按需补充) type TFilterOperator = '==' | '!=' | '<' | '<=' | '>' | '>=' | 'in' | 'not-in'; // 泛型版本的Hook参数类型 type UseDocsCountOptions<T> = { collectionRef: string; filter?: { filterKey: keyof T; // 限制为目标接口的字段名 operator: TFilterOperator; filterValue: T[keyof T]; // 值的类型为接口所有字段类型的联合 }; }; // 泛型Hook的实现(原逻辑不变) function useDocsCount<T>(options: UseDocsCountOptions<T>) { // 你的Hook原有代码 // ... }
2. 调用时指定泛型参数
用Hook的时候,把对应的接口作为泛型参数传进去,TypeScript会自动帮你做类型校验:
// 调用时指定泛型为INotification const count = useDocsCount<INotification>({ collectionRef: 'notifications', filter: { filterKey: 'seen', // 只能写INotification里有的字段,写错直接报错 operator: '==', filterValue: false, // 必须是boolean类型,传字符串或者数字都会报错 } }); // 错误示例:filterKey不是INotification的字段 const count = useDocsCount<INotification>({ collectionRef: 'notifications', filter: { filterKey: 'invalidKey', // TypeScript直接报错:这个值不属于keyof INotification operator: '==', filterValue: false } }); // 错误示例:filterValue类型不匹配 const count = useDocsCount<INotification>({ collectionRef: 'notifications', filter: { filterKey: 'seen', operator: '==', filterValue: "false" // TypeScript报错:string不能赋值给boolean } });
3. 更精准的类型关联(可选)
如果想要filterValue的类型完全匹配当前选中的filterKey的类型(而不是所有字段类型的联合),可以把参数类型再优化一下:
type UseDocsCountOptions<T> = { collectionRef: string; filter?: { // 遍历接口的每个字段,生成对应的键值类型组合 [K in keyof T]: { filterKey: K; operator: TFilterOperator; filterValue: T[K]; }; }[keyof T]; // 展开为所有可能的键值组合的联合类型 };
这样改了之后,当你选dateCreated作为filterKey时,filterValue就必须是Timestamp类型;选text时,就必须是string类型,完全贴合接口里的字段类型,约束更精准。
内容的提问来源于stack exchange,提问作者User123123
相关产品推荐
相关产品推荐

