reactjs - Högre ordningens komponent med generiska inception-typer


Jag har en generell unionstyp för att hämta data som heter RemoteData och för den försökte jag skapa en högre ordningskomponent:

export interface IWithRemote<T> {
  remote: RemoteData<T>;
}

export interface IWithData<T> {
  data: T;
}

export function withRemoteData<T, K extends IWithData<T>>(XComponent: React.ComponentType<K>) {
  return class extends React.Component<IWithRemote<T>> {
    render() {
      const { remote } = this.props;
      switch (remote.kind) {
        case RemoteDataKind.NotAsked:
          return <div> nice not asked yet </div>;
        case RemoteDataKind.Loading:
          return <Spinner />;
        case RemoteDataKind.Success:
          return <XComponent data={remote.data} />
        case RemoteDataKind.Failure:
          return <div>daaaamn</div>;

        default:
          return assertNever(remote);
      }
    }
  };
}

Men dessa typer fungerar inte. Jag är inte säker på om det jag försöker göra ens är möjligt, hur som helst får jag

TS2322: Typen '{ data: T; }' kan inte tilldelas typen 'K'.

När jag försöker tilldela remote.data till data i XComponent.

Här hittar du all kod som behövs för att återskapa problemet. Finns det ett sätt att fixa dessa typer?

Svar #1

Du skulle helt enkelt kunna göra

export function withRemoteData<T>(
    XComponent: React.ComponentType<IWithData<T>>
) {
    return class extends React.Component<IWithRemote<T>> {
        render() {
            const { remote } = this.props;
            switch (remote.kind) {
                case RemoteDataKind.NotAsked:
                    return <div> nice not asked yet </div>;
                case RemoteDataKind.Loading:
                    return <div> spinnnnnner </div>;
                case RemoteDataKind.Success:
                    return <XComponent data={remote.data} />;
                case RemoteDataKind.Failure:
                    return <div>daaaamn</div>;

                default:
                    return assertNever(remote);
            }
        }
    };
}

https://codesandbox.io/s/react-typescript-playground-38bnw

Svar #2

Parametertypen för HOC, XComponent, förväntar sig att dess egenskaper överensstämmer med någon typ K, som kan innehålla några extra egenskaper utöver data.

Men props-typen för HOC:en, IWithRemote<T>, har remote-egenskapen som är deklarerad att endast ha data när kind är Success, inget annat.

Så, när du renderar XComponent med

<XComponent data={remote.data} />

Var ska alla andra egenskaper i K som XComponent förväntas ta emot för sina props komma ifrån?

Hur som helst, om allt som XComponent behöver kommer samman med data i remote när kind är Success, kan du deklarera att det faktiskt är så - du behöver bara lägga till den K-parametern till RemoteData och IWithRemote, och deklarera Success union-medlem som en snitt-typ

   {
            kind: RemoteDataKind.Success;
      } & K

De fullständiga typerna är

type RemoteData<T, K extends IWithData<T>> =
    | {
            kind: RemoteDataKind.NotAsked;
      }
    | {
            kind: RemoteDataKind.Loading;
      }
    | {
            kind: RemoteDataKind.Success;
      } & K
    | {
            kind: RemoteDataKind.Failure;
            error: Error;
      };

interface IWithRemote<T, K extends IWithData<T>> {
    remote: RemoteData<T, K>;
}

interface IWithData<T> {
    data: T;
}

export function withRemoteData<T, K extends IWithData<T>>(
    XComponent: React.ComponentType<K>
) {
    return class extends React.Component<IWithRemote<T, K>> {

När du sedan renderar XComponent behöver du se till att allt som finns i remote skickas till det, inte bara data:

            case RemoteDataKind.Success:
                return <XComponent {...remote} />;