如何根据函数参数配置映射组件Props的正确函数返回类型
类型问题:如何让组件映射函数返回正确的Props类型
我有一个接收固定配置对象的函数,配置对象示例如下:
const config = { foo: { component: MyComponent1, }, bar: { component: MyComponent2, }, };
我希望该函数返回如下结构的对象:
const returned = { foo: WrappedMyComponent1, bar: WrappedMyComponent2, };
其中ComponentN与WrappedComponentN拥有相同的Props(组件类型基本一致)。
以下是我的尝试代码:
import { Box, TextField } from '@mui/material'; const remapComponents = <P, C extends Record<string, { component: ComponentType<P> }>, R extends Record<keyof C, ComponentType<P>>>(config: C): R => Object.entries(config).reduce((prevValue, [key, conf]) => ({ ...prevValue, [key]: (conf as { component: ComponentType<P> }).component, }), EMPTY_DATA_OBJ as R); const result = remapComponents({ foo: { component: TextField }, bar: { component: Box } });
目前我能获取到result中正确的键(如result.foo、result.bar),但所有组件的类型均为ComponentType<unknown>,请问如何让返回的组件拥有正确的Props类型?
解决方案
问题出在泛型定义过于单一,P被强制作为所有组件共用的Props类型,无法区分每个组件的独立Props。需要让泛型针对配置对象的每个键,单独推断对应组件的Props类型,而非用统一的P约束。
优化后的代码
import { ComponentType } from 'react'; import { Box, TextField } from '@mui/material'; // 定义单个配置项的类型 type ComponentConfig<T extends ComponentType<any>> = { component: T; }; // 核心映射函数 const remapComponents = <Config extends Record<string, ComponentConfig<any>>>( config: Config ): { [K in keyof Config]: Config[K]['component'] } => { return Object.entries(config).reduce((acc, [key, conf]) => { acc[key as keyof Config] = conf.component; return acc; }, {} as { [K in keyof Config]: Config[K]['component'] }); }; // 使用示例 const result = remapComponents({ foo: { component: TextField }, bar: { component: Box } }); // 此时类型推断完全正常: // result.foo 对应 ComponentType<TextFieldProps> // result.bar 对应 ComponentType<BoxProps>
关键改进点
- 移除统一泛型
P:让TypeScript自动推断每个配置项中组件的原生Props类型,避免强制统一导致的unknown丢失。 - 精准映射返回类型:用
{ [K in keyof Config]: Config[K]['component'] }明确告知TS,返回对象的每个键类型与原配置中对应键的component类型完全一致,完整保留原组件的Props信息。 - 安全类型断言:在
reduce中使用key as keyof Config确保键的类型合法性,避免不必要的类型损耗。
若需添加组件包装逻辑
如果实际需求是给每个组件添加统一包装(比如示例中的WrappedMyComponent),只需在映射时替换为包装后的组件,类型会自动继承原组件Props:
const wrapComponent = <T extends ComponentType<any>>(Component: T) => { return (props: React.ComponentProps<T>) => { // 这里可添加统一包装逻辑,比如外层套容器、添加通用事件等 return <Box padding={1}><Component {...props} /></Box>; } as T; // 断言为原组件类型,保留Props推断能力 }; const remapComponents = <Config extends Record<string, ComponentConfig<any>>>( config: Config ): { [K in keyof Config]: Config[K]['component'] } => { return Object.entries(config).reduce((acc, [key, conf]) => { acc[key as keyof Config] = wrapComponent(conf.component); // 替换为包装后的组件 return acc; }, {} as { [K in keyof Config]: Config[K]['component'] }); };
内容的提问来源于stack exchange,提问作者Николай Жигалин
相关产品推荐
相关产品推荐

