TypeScript键重映射时如何访问泛型类型以实现类型安全
TypeScript 对象键重映射与泛型类型安全实现
问题描述
需要实现工具函数,将传入的资源对象的键从key重映射为useKey格式,同时将每个资源转换为带泛型参数的函数,确保最终生成的Hook具备完整类型安全。
示例用法:
const { useMyAwesomeResource } = handleResources({ resources: { // TProps: string, TItem: AwesomeResource myAwesomeResource: handleResource<string, AwesomeResource>({ // 自定义配置 }) } })
期望最终类型:
useMyAwesomeResource: (resourceId: string) => HookResult<AwesomeResource>
当前实现中,HookFuncs类型无法正确访问每个资源携带的泛型TProps和TItem,导致类型丢失,核心问题代码如下:
// 结果类型定义 type HookResult<TItem> = { data: TItem } type HookFunc<TItem, TProps> = (props: TProps) => HookResult<TItem> type HookFuncs<TResourceHandlers> = { [Key in keyof TResourceHandlers as `use${Capitalize< string & Key >}`]: HookFunc<TResourceHandlers[Key]> // 此处无法正确提取泛型参数 } // 配置类型 type ResourceHandler<TRawItem, TItem> = {} interface Config { resources: { [resourceName: string]: ResourceHandler<any, any> } } export const handleResources = ( config: Config ): HookFuncs<typeof config.resources> => { // 逻辑实现 }) // 资源创建函数 const handleResource = <TItem, TProps = {}>( resourceConfig: ResourceConfig<TItem, TProps> ): ResourceInfo<TItem, TProps> => resourceConfig
解决方案
核心是让每个资源处理器携带明确的泛型类型信息,同时将handleResources改为泛型函数,保留传入资源的完整类型,再通过条件类型提取每个资源的TProps和TItem:
1. 修正资源相关类型定义
确保ResourceInfo(handleResource的返回类型)明确携带TProps和TItem:
// 自定义资源配置类型(可根据实际需求补充字段) type ResourceConfig<TItem, TProps> = { // 比如请求地址、转换逻辑等配置项 } // 资源处理器类型,用于携带泛型参数信息 type ResourceInfo<TProps, TItem> = { _propsType?: TProps _itemType?: TItem } // Hook返回结果类型 type HookResult<TItem> = { data: TItem } // Hook函数类型 type HookFunc<TProps, TItem> = (props: TProps) => HookResult<TItem>
2. 实现带泛型提取的HookFuncs类型
通过条件类型从ResourceInfo中提取TProps和TItem:
// 提取ResourceInfo中的TProps类型 type ExtractProps<T> = T extends ResourceInfo<infer P, any> ? P : never // 提取ResourceInfo中的TItem类型 type ExtractItem<T> = T extends ResourceInfo<any, infer I> ? I : never // 重映射键并生成对应的Hook类型 type HookFuncs<TResources extends Record<string, ResourceInfo<any, any>>> = { [Key in keyof TResources as `use${Capitalize<string & Key>}`]: HookFunc< ExtractProps<TResources[Key]>, ExtractItem<TResources[Key]> > }
3. 修正handleResources为泛型函数
不再使用固定的Config接口,用泛型保留传入资源的完整类型:
export const handleResources = <TResources extends Record<string, ResourceInfo<any, any>>>( config: { resources: TResources } ): HookFuncs<TResources> => { const result = {} as HookFuncs<TResources> Object.entries(config.resources).forEach(([key, resource]) => { const hookKey = `use${key.charAt(0).toUpperCase() + key.slice(1)}` as keyof HookFuncs<TResources> result[hookKey] = (props) => { // 替换为实际资源处理逻辑,比如请求数据并返回HookResult return { data: {} as ExtractItem<typeof resource> } } }) return result }
4. 修正handleResource函数
确保返回类型正确关联泛型参数:
const handleResource = <TProps, TItem>( resourceConfig: ResourceConfig<TItem, TProps> ): ResourceInfo<TProps, TItem> => { return { ...resourceConfig } as ResourceInfo<TProps, TItem> }
测试示例
type AwesomeResource = { id: string; name: string } const { useMyAwesomeResource } = handleResources({ resources: { myAwesomeResource: handleResource<string, AwesomeResource>({}) } }) // useMyAwesomeResource类型自动推导为:(props: string) => HookResult<AwesomeResource> const result = useMyAwesomeResource('123') // result.data 类型为 AwesomeResource,具备完整类型校验
关键说明
- 通过
ResourceInfo携带泛型类型信息,让TypeScript可以通过infer关键字提取对应的TProps和TItem handleResources使用泛型参数TResources保留传入资源的完整类型,避免用any丢失类型信息- 键重映射通过
as关键字结合Capitalize工具类型实现,确保生成的Hook名称符合约定
内容的提问来源于stack exchange,提问作者jared_hexagon
相关产品推荐
相关产品推荐

