如何拆分TypeScript交叉类型Props并传递给两个子组件?
拆分交叉类型Props并传递给子组件的技巧
场景说明
现有两个子组件,各自定义了复杂的Props类型:
interface Child1Props { foo: string; bar: string; baz: string } interface Child2Props { foo: string; qux: string; quux: string } const Child1 = (props: Child1Props) => {}; const Child2 = (props: Child2Props) => {};
父组件接收两个子组件Props的交叉类型作为自身Props,希望无需显式写出每个属性,直接用扩展运算符将对应Props传递给子组件,避免像下面这样手动拆分:
const Parent = ({foo, bar, baz, ...rest}: Child1Props & Child2Props) => ( <Child1 foo={foo} bar={bar} baz={baz} > <Child2 foo={foo} {...rest} /> </Child1> )
目标是让父组件完全不感知子组件的具体Props,实现类似如下的简洁写法:
const Parent = (props: Child1Props & Child2Props) => ( <Child1 {...child1Props} > <Child2 {...child2Props} /> </Child1> )
可行解决方案
方案1:工具函数+类型安全的键数组拆分
通过封装工具函数,结合预定义的子组件Props键数组(用TS语法确保键的正确性),实现自动过滤拆分:
// 通用Props过滤工具函数 const pickProps = <T extends object>(props: object, keys: (keyof T)[]): T => { const result = {} as T; keys.forEach(key => { if (key in props) { result[key] = props[key as keyof typeof props] as T[keyof T]; } }); return result; }; // 预定义子组件的Props键名,用satisfies确保键属于对应Props类型(TS 4.9+支持) const child1Keys = ['foo', 'bar', 'baz'] as const satisfies (keyof Child1Props)[]; const child2Keys = ['foo', 'qux', 'quux'] as const satisfies (keyof Child2Props)[]; const Parent = (props: Child1Props & Child2Props) => { const child1Props = pickProps<Child1Props>(props, child1Keys); const child2Props = pickProps<Child2Props>(props, child2Keys); return ( <Child1 {...child1Props}> <Child2 {...child2Props} /> </Child1> ); };
如果使用TS 4.9以下版本,可将键数组改为类型断言:const child1Keys = ['foo', 'bar', 'baz'] as (keyof Child1Props)[]
方案2:基于组件类型的通用提取函数
利用React的ComponentProps工具类型,封装通用函数提取对应组件的Props:
import type { ComponentProps } from 'react'; // 接收组件和原始Props,返回该组件所需的Props const extractComponentProps = <C extends React.ComponentType<any>>( Component: C, props: object ): ComponentProps<C> => { const result = {} as ComponentProps<C>; // 遍历原始Props,过滤出属于目标组件的属性 (Object.keys(props) as (keyof ComponentProps<C>)[]).forEach(key => { result[key] = props[key as keyof typeof props] as ComponentProps<C>[keyof ComponentProps<C>]; }); return result; }; // 使用示例 const Parent = (props: Child1Props & Child2Props) => { const child1Props = extractComponentProps(Child1, props); const child2Props = extractComponentProps(Child2, props); return ( <Child1 {...child1Props}> <Child2 {...child2Props} /> </Child1> ); };
注意:此方案如果原始Props包含子组件不需要的额外属性,会被错误传递,建议结合键数组过滤优化。
方案3:基于TS类型推断的自动拆分
通过TS的Pick类型定义拆分后的Props类型,再通过运行时判断自动分配属性:
type ParentProps = Child1Props & Child2Props; // 从交叉类型中提取对应子组件的Props类型 type ExtractChild1Props = Pick<ParentProps, keyof Child1Props>; type ExtractChild2Props = Pick<ParentProps, keyof Child2Props>; // 拆分Props的辅助函数 const splitProps = (props: ParentProps): { child1: ExtractChild1Props; child2: ExtractChild2Props } => { const child1 = {} as ExtractChild1Props; const child2 = {} as ExtractChild2Props; Object.keys(props).forEach(key => { const k = key as keyof ParentProps; // 通过空对象判断键是否属于对应子组件的Props if (k in ({} as Child1Props)) { child1[k] = props[k]; } if (k in ({} as Child2Props)) { child2[k] = props[k]; } }); return { child1, child2 }; }; const Parent = (props: ParentProps) => { const { child1: child1Props, child2: child2Props } = splitProps(props); return ( <Child1 {...child1Props}> <Child2 {...child2Props} /> </Child1> ); };
注意:如果子组件包含可选属性,空对象判断会失效,此时建议结合方案1的键数组方式。
总结
如果要让父组件完全不感知子组件的具体Props,最可靠的方式是封装通用的Props提取工具函数,并配合类型安全的键数组,既保证TS类型检查,又避免手动编写属性名。
内容的提问来源于stack exchange,提问作者mbosecke
相关产品推荐
相关产品推荐

