likes
comments
collection
share

ComponentType、ComponentProps、typeof傻傻分不清楚

作者站长头像
站长
· 阅读数 15

前言

每次写高阶函数总是经常用到ComponentType和ComponentProps,总是用得感觉懂点又不是完全懂,你说一点不懂呢。不可能🙅、绝对不可能!!!但是每次用的时候又不知道怎么使用。今天一起来彻底搞清楚。

export const withAlert = (
  withAlertconfig: WithAlertConfig,
) => {
  return function <T,R = React.ComponentProps<React.ComponentType<T>>>(
    ModalComponent: React.ComponentType<T>,
  ) {
    return (config: R) => {
      return <ModalComponent {...config} />;
    }
  }
}

ComponentType、ComponentProps、typeof傻傻分不清楚

如果能看懂上面函数的类型,那么你可以越过本文章。如果你不是很清楚或者T,R分别代表什么,那么你可以花一点点时间耐心阅读下,或许你会有收获。

前置知识

类组件与函数式组件

类组件指的是使用class语法定义的React组件,例如:

class App extends React.Component {
  render() {
    return <div>Hello</div>
  }
}

这里App就是一个组件类,它有以下特点:

  • 使用class语法定义
  • 继承自React.Component
  • 持有状态state和生命周期方法
  • 渲染UI通过实现render方法
  • 可以添加其他自定义方法
  • 使用实例化组件

组件类和函数组件(const App = () => {})的区别在于:

  • 类组件是class,函数组件是函数
  • 类组件可以管理状态,函数组件通过hooks管理状态
  • 类组件有生命周期方法,函数组件有Effect Hook

组件类与类组件

类组件(Class Component)

  • 使用 ES6 class 语法定义的 React 组件
  • 包含继承自 React.Component 的类定义
  • 例如:

class MyComponent extends React.Component {
  //...
}

组件类 (Component Class)

  • 更广义的说法,表示所有组件的类/类型
  • 比如函数组件也有对应的函数类型
  • 所以函数组件的类型也可称为组件类

区别

  • 类组件具体指用 class 定义的组件
  • 组件类泛指所有组件的类型/类

关系

  • 类组件是一种组件类,是更具体的说法
  • 组件类是更广泛的概念

所以综上:

  • 类组件是具体的 class 组件
  • 组件类是泛指组件的类型

二者可以看作一个普通名词和一个更泛泛的概念的关系。

直入主题 - 三者区别

typeof MyComponent

  • 表示组件类MyComponent本身的类型
  • 是个函数类型,包含调用签名、静态属性等
  • 用于描述组件类,表示传入props可以实例化出组件

React.ComponentType

  • React提供的公共类型,通用表示一个组件类的类型
  • 例如:type AppType = React.ComponentType<AppProps>
  • 作用和typeof基本一致,都表示组件类类型

React.ComponentProps

  • 用于提取组件类的props类型,得到一个接口类型
  • 例如:type AppProps = React.ComponentProps<typeof App>
  • 和前两个表示组件类类型不同,表示实例的props类型

总结:

  • typeof Component 或 React.ComponentType :组件类类型
  • React.ComponentProps :组件props类型
ComponentType、ComponentProps、typeof傻傻分不清楚

看到这里是不是还是有点懵逼,接下来来几个生动的例子

typeof 函数式组件 VS typeof 类组件

定义一个函数式组件与一个类组件

class ClassComponet extends React.Component<{title:string}> {
  render() {
    return <div>ClassComponet</div>;
  }
}
const FunctionComponet = (props:{title:string}) => {
  return <div>FunctionComponet</div>;
}

type ClassComponetType = typeof ClassComponet;

这里的ClassComponetType会被推断如下类型:

interface MyComponentType {
    (props: {title:string}): ReactElement;
    propTypes?: WeakValidationMap<{title:string}>;
    // ...其他静态属性 
}

type FunctionComponetType = typeof FunctionComponet

这里的FunctionComponetType会被推断如下类型:

(props: {title: string}) => React.ReactElement;

React.ComponentType

类组件

type ClassComponentType = React.ComponentType<typeof ClassComponent>

type ClassComponentType = {
  new(props: Props, context?: any): React.Component<Props>;

  propTypes?: PropTypes;
  defaultProps?: Partial<Props>;
  displayName?: string; 
  // ...其他静态属性
}

函数式组件

React.ComponentType会报错,具体原因如下:

在react-jsx.d.ts 中,React.ComponentType 定义如下:

interface ComponentType<P = {}> {
  new (props: P, context?: any): Component<P, any>;
  propTypes?: WeakValidationMap<P>;
  contextTypes?: ValidationMap<any>;
  defaultProps?: Partial<P>;
  displayName?: string;
}

export type ComponentClass<P = {}, S = ComponentState> = ClassType<
  P,
  S,
  Component<P, S>
>;

export type ComponentType<P = {}> = ComponentClass<P>;

它包含一个构造函数签名,接收 props 和 context;函数组件是纯函数,不能被构造,所以传入会报错。需要获取函数组件的组件类直接使用typeof FunctionComponent即可

React.ComponentProps

函数组件的Props的类型

type A = typeof FunctionComponet
type B = React.ComponentProps<typeof FunctionComponet>

这里的 A 和 B 的类型是等价的,都表示 Component 组件的 props 类型 Props

类组件的Props类型

type C = React.ComponentProps<typeof ClassComponet>

type D = React.ComponentProps<React.ComponentType<typeof ClassComponet>>
  • 方式1获取实例 props 类型,不能扩展
  • 方式2获取类类型的静态 props 类型,可以扩展 👍

现在我们回头来看下我们最开始定义的函数,

export const withAlert = (
  withAlertconfig: WithAlertConfig,
) => {
  return function <T,R = React.ComponentProps<React.ComponentType<T>>>(
    ModalComponent: React.ComponentType<T>,
  ) {
    return (config: R) => {
       // 巴拉巴拉 这里是自己的业务逻辑
       <ModalComponent {...config} />;
    }
  }
}

下面的使用例子很重要,你看完就会明白withAlert函数的作用。

type ModalProps = {
  title?:React.Node;
  open:boolean;
}
// 定义一个Modal组件,也可以是Antd 的Modal
const Modal:React.FC<AntdModalProps & { 
  name:string;
  // 这里是自己定义的一些参数
}>  = ({name,...rest}) => {
  return <AntdModal {...rest}>
    // 这里是弹框里面的内容,可以自己定义
  </AntdModal>
}
    
// 这里传入一些默认的配置,如组件打开的参数,点击确认的事件等,可以将此函数暴露为一个通用的函数
const launchModal = withAlert({
  visibleKey: 'open',
  okEventKey: 'onOk',
  cancelEventKey: 'onCancel',
  afterCloseEventKey: 'afterClose',
});

// 每次使用只需要使用launchModal返回一个打开弹框的函数
const alert_modal = launchModal(Modal)  
    
// 打开弹框
<button onClick={()=>{
  alert_modal({
    title:'审批',
    onFinish:async (values)=>{
      return Promise.resolve('ok')
    },
    name:'章三'
  })
}}>打开弹框</button>
   
  • withAlert是一个函数传入类型为WithAlertConfig的withAlertconfig参数,函数返回了一个函数。

  • 返回函数接收一个组件作为参数,然后返回一个可以打开弹框的方法。

  • function <T,R = React.ComponentProps<React.ComponentType<T>>>这里的T就是ModalProps这里的R指的是通过组件类获取到的Props的类型,其实就是可扩展的ModalProps

  • 将R作为alert_modal的config的类型,其实就是ModalProps.

  • 剩下的就是你自己的逻辑,你可以通过事件打开弹框,并设置open或者visible参数,这样就不需要手动维护open或者visible

总结

哪有什么总结,总结在上面👆直入主题 - 三者区别

你以为就完了,其实没有👇分享一些小心得

  • 使用高阶函数创建一个让所有组件都拥有请求能力的WithAction。例如:WithAction(Button)、WithAction(Select)

  • 使用高阶组件封装一个不需要维护open/visible状态的WithAlert。例如:WithAlert(Modal)、WithAlert(Draw)

ComponentType、ComponentProps、typeof傻傻分不清楚

文章如有理解错误🙅的地方请大佬轻点喷🙇