TypeScript创建严格类型React HOC包装RN组件的类型报错问题
问题背景
尝试创建高阶组件(HOC)包装React Native组件,基于组件ref添加布局测量功能。未加类型的JS实现示例如下:
export function withHelper(WrappedEl) { return ({ layoutParam, onChangeLayout, ...wrappedElProps }) => { const ref = useRef(null); const onLayout = useCallback(() => { ref.current?.measure((_x, _y, width, height, px, py) => { onChangeLayout({ layout: { width, height, y: py, x: px, }, layoutParam, }); }); }, [ onChangeLayout, layoutParam, ]); return <WrappedEl {...wrappedElProps} ref={ref} onLayout={onLayout} />; }; }
代码中调用的measure是React Native元素从NativeMethods接口继承的方法,用于获取元素渲染后的位置、尺寸信息。
需求目标
需要实现两点严格类型约束:
- 该HOC仅可用于ref实例带有
measure方法的元素 - HOC返回的组件需正确透传原组件所有props,同时新增
layoutParam、onChangeLayout两个必填props
问题复现
当前编写的TS实现代码如下:
interface WithHelperProps { layoutParam: string; onChangeLayout: (props: {layout: Layout, payoutParam: string}) => void; } export function withHelper< P extends object, V extends React.ComponentClass<P> & Constructor<NativeMethods>, >(El: V): React.FC<P & WithHelperProps> { return ({ layoutParam, onChangeLayout, ...props }) => { const ref = useRef<InstanceType<V>>(null); const onLayout = useCallback(() => { ref.current?.measure((_x, _y, width, height, px, py) => { onChangeLayout({ layout: { width, height, y: py, x: px, }, layoutParam, }); }); }, [ onChangeLayout, layoutParam, ]); const passthroughProps: Omit< PropsWithChildren<P & WithHelperProps>, keyof WithHelperProps > = props; return <El {...passthroughProps} ref={ref} onLayout={onLayout} />; }; }
编译时报错如下:
Type 'Omit<PropsWithChildren<P & WithHelperProps>, keyof WithHelperProps> & { ref: RefObject<InstanceType<V>>; onLayout: () => void; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<P, any, any> & NativeMethods> & LibraryManagedAttributes<...>'. Type 'Omit<PropsWithChildren<P & WithHelperProps>, keyof WithHelperProps> & { ref: RefObject<InstanceType<V>>; onLayout: () => void; }' is not assignable to type 'LibraryManagedAttributes<V, Readonly<P>>'.
问题根因
报错由三个问题共同导致:
- 笔误:
WithHelperProps接口中onChangeLayout的参数属性写为payoutParam,和实际代码中传入的layoutParam不一致 - 泛型约束范围过窄:仅约束了类组件类型,没有覆盖React Native内置原生组件(View/Text等属于HostComponent,不是普通ComponentClass)、forwardRef包装的组件
- 冗余类型标注错误:手动给
passthroughProps做Omit类型标注时,没有兼容React对组件props做的defaultProps处理、属性过滤等LibraryManagedAttributes逻辑,手动标注的类型和React实际期望的props类型不兼容
修复后实现
import React, { useRef, useCallback, ComponentRef } from 'react'; import type { LayoutRectangle, NativeMethods, LayoutChangeEvent } from 'react-native'; interface WithHelperExtraProps { layoutParam: string; onChangeLayout: (data: { layout: LayoutRectangle; layoutParam: string }) => void; } export function withHelper< T extends React.ComponentType<{ onLayout?: (e: LayoutChangeEvent) => void }> >(WrappedComponent: T) { // 静态类型校验:传入组件的ref必须包含NativeMethods的measure方法,不满足则编译报错 type WrappedInstance = ComponentRef<T>; const _typeCheck: NativeMethods extends WrappedInstance ? unknown : never = 0 as unknown; type ResultProps = Omit<React.ComponentProps<T>, keyof WithHelperExtraProps> & WithHelperExtraProps; const WrappedWithHelper = ({ layoutParam, onChangeLayout, ...restProps }: ResultProps) => { const ref = useRef<WrappedInstance & NativeMethods>(null); const onLayout = useCallback(() => { ref.current?.measure((_x, _y, width, height, pageX, pageY) => { onChangeLayout({ layout: { width, height, x: pageX, y: pageY, }, layoutParam, }); }); }, [onChangeLayout, layoutParam]); return <WrappedComponent {...restProps} ref={ref} onLayout={onLayout} />; }; return WrappedWithHelper; }
实现说明
- 泛型直接约束传入组件为支持
onLayout属性的组件类型,覆盖类组件、函数组件、forwardRef组件、RN原生宿主组件所有场景 - 通过
ComponentRef<T>提取传入组件的ref实例类型,加静态类型校验确保实例继承NativeMethods接口,满足「仅可用于带measure方法元素」的约束 - 去掉冗余的
passthroughProps手动类型标注,直接透传剩余props,由TS自动推导类型匹配React的props处理逻辑,避免LibraryManagedAttributes类型不兼容问题 - 修正了原接口中
payoutParam的笔误,统一为layoutParam - 返回值的props类型通过
Omit<原组件Props, HOC注入Props> & HOC新增Props计算,确保既透传原组件所有属性,又强制要求传入新增的两个props
内容的提问来源于stack exchange,提问作者Martijn Arts
相关产品推荐
相关产品推荐

