Compile-time checking of component parameters

As the TypeScript language features have improved, our team’s front-end code has migrated to TypeScript entirely. Current TypeScript fully supports JSX syntax, for example:

import * as React from 'react' type Props = { p1: string } class ComponentA extends React.Component<Props> { render(){ return <div>{this.props.p1}</div> } } <ComponentA <ComponentA P1 ="test"/> // correctCopy the code

This allows our declared ComponentA component to check that the incoming parameters are correct at compile time.

If we want a component parameter to be optional, we simply place a question mark before the parameter definition:

import * as React from 'react' type Props = { p1? : string } class ComponentA extends React.Component<Props> { render(){ return <div>{this.props.p1 || "empty"}</div> } } <ComponentA /> <ComponentA p1="test"/> //Copy the code

High order component

In the process of using React, existing components are inevitably reencapsulated to form many higher-order components.

In this case, the first solution that comes to mind is to write standalone higher-order components such as:

import * as React from 'react'

type BCProps = {
  p1: string
}

class BC extends React.Component<BCProps> {
  render(){
    return <div>{this.props.p1}</div>
  }
}

type HOCProps = {
  p1h:string
}

class HOC extends React.Component<HOCProps> {
  render(){
    return <BC p1={this.props.p1h}/>
  }
}
Copy the code

In the example above, BC is the base component and HOC is the higher-order component that we encapsulate. We can see that BCProps and HOCProps are independent of each other. The advantages of this are as follows:

  1. It has wide applicability and can be used in any scenario.
  2. Fully decoupled, users only need to know HOCProps, not BCProps.

However, its disadvantages are also very obvious. Because BC is fully encapsulated, when BCProps is changed, for example, p1 is changed to P1B, we need to change the code of HOC, otherwise the operation will report an error.

For a very common example, a base component with its own styling doesn’t fit our needs, and we want to be able to encapsulate a higher-order component with a custom styling, while the other Props remain the same. In this way, the higher-order components’ code does not need to be modified while the style parameters are defined the same and the other parameters change.

In JS, this parameter passing is very simple, but in TS it can be a bit of a hassle. The clumsiest way to do this would be to manually copy the style independent parameters in the BCProps, but then if the BCProps were modified, we would need to manually modify the HOCProps, without the non-style parameters being changed and the code not needing to be modified.

In the current TS release, we cannot directly delete properties of existing types. To implement this higher-order component, we first need to define two help types:

type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];

type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
Copy the code

In this way, we can implement higher-order components with no style parameters:

import * as React from 'react' type BCProps = { p1: string, style: React.CSSProperties } class BC extends React.Component<BCProps> { render(){ return <div style={this.props.style}>{this.props.p1}</div> } } type HOCProps = Omit<BCProps, "style"> class HOC extends React.Component<HOCProps> { render(){ return <BC {... Object.assign({},this.props,{style:{color:"red"}})}/> } }Copy the code

HOC does not need to change when BC is modified, as long as the style parameter is used the same way.