@types/react 关于 ComponentProps 工具类型如何选择

15 阅读1分钟

@types/react 包中获取 ComponentProps 有几个工具类型(Utility Type) 可以使用 ComponentProps v.s ComponentPropsWithRef v.s ComponentPropsWithoutRef, 本文介绍了该如何选择这三个工具类型.

TLDR | 长话短说

现代 React 应用, 只使用 Function Component 的情况下: 可以忘掉 ComponentPropsWithRef, 不需要 ref 时使用 ComponentPropsWithoutRef, 其他情况使用 ComponentProps.

包版本

本文以 react@19 & @types/react@19 进行说明

定义摘抄

node_modules/.pnpm/@types+react@19.2.9/node_modules/@types/react/index.d.ts

type PropsWithoutRef<Props> =
    // Omit would not be sufficient for this. We'd like to avoid unnecessary mapping and need a distributive conditional to support unions.
    // see: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
    // https://github.com/Microsoft/TypeScript/issues/28339
    Props extends any ? ('ref' extends keyof Props ? Omit<Props, 'ref'> : Props) : Props


type JSXElementConstructor<P> =
  | ((props: P) => ReactNode | Promise<ReactNode>)
  // constructor signature must match React.Component
  | (new (props: P, context: any) => Component<any, any>)
    
// --------------------------------

type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =
  T extends JSXElementConstructor<infer Props>
    ? Props
    : T extends keyof JSX.IntrinsicElements
      ? JSX.IntrinsicElements[T]
      : {}
      
type ComponentPropsWithRef<T extends ElementType> =
    T extends JSXElementConstructor<infer Props>
      ? // If it's a class i.e. newable we're dealing with a class component
        T extends abstract new (args: any) => any
          ? PropsWithoutRef<Props> & RefAttributes<InstanceType<T>>
          : Props
      : ComponentProps<T>
      
type ComponentPropsWithoutRef<T extends ElementType> = PropsWithoutRef<ComponentProps<T>>;

从实现上看

  1. ComponentProps 是从 JSXElementConstructor上 infer function-signature or class-signature
  2. ComponentPropsWithoutRef 是从 ComponentProps + 类似 omit 逻辑.
  3. ComponentPropsWithRef 对 class component 有特殊照顾.

从实例看

IntrinsicElement

  • ComponentProps<'div'> => React.JSX.IntrinsicElements['div']
  • ComponentPropsWithRef<'div'>ComponentProps<'div'> 完全一致

FunctionComponent

function Greeting({name}: {name: string}) {
  return <div>Hello, {name}</div>
}
  • ComponentProps<Greeting> => Parameters<typeof Greeting>[0]
  • ComponentPropsWithRef<Greeting>ComponentProps<Greeting> 完全一致

class component

  • ComponentProps<ClassComponent> 获取 Props 类型
  • ComponentPropsWithRef<ClassComponent> => PropsWithoutRef<ComponentProps<ClassComponent>> & RefAttributes<InstanceType<T>

结论

如果只使用 function component + intrinsic elements 的情况下

  • ComponentPropsWithRefComponentProps 完全一致.
  • 可以忘掉 ComponentPropsWithRef: 不需要 ref 时使用 ComponentPropsWithoutRef, 其他情况使用 ComponentProps