Heim > Web-Frontend > js-Tutorial > So verwenden Sie TypeScript zum Akkumulieren von Typen: Eingabe ALLER möglichen fetch()-Ergebnisse

So verwenden Sie TypeScript zum Akkumulieren von Typen: Eingabe ALLER möglichen fetch()-Ergebnisse

Barbara Streisand
Freigeben: 2024-12-06 12:40:15
Original
880 Leute haben es durchsucht

How to Use TypeScript to Accumulate Types: Typing ALL possible fetch() Results

Als ich (mit meinem Team) anfing, unsere Anwendung in TypeScript und Svelte neu zu schreiben (es war in JavaScript und React, was wir alle hassen), stand ich vor einem Problem:

Wie kann ich alle möglichen Textkörper einer HTTP-Antwort sicher eingeben?

Kommt Ihnen das bekannt? Wenn nicht, bist du höchstwahrscheinlich „einer von denen“, hehe. Lassen Sie uns einen Moment abschweifen, um das Bild besser zu verstehen.

Warum dieses Gebiet unerforscht zu sein scheint

Niemand scheint sich für „alle möglichen Körper“ einer HTTP-Antwort zu interessieren, da ich nichts finden konnte, was bereits dafür gemacht wurde (naja, vielleicht ts-fetch). Lassen Sie mich hier kurz meine Logik durchgehen, warum das so ist.

Niemand kümmert sich darum, weil die Leute auch:

  1. Kümmere dich nur um den glücklichen Weg: Der Antworttext, wenn der HTTP-Statuscode 2xx ist.

  2. Die Leute geben es manuell woanders ein.

Zu Punkt 1 würde ich sagen: Ja, Entwickler (insbesondere die unerfahrenen) vergessen, dass eine HTTP-Anfrage fehlschlagen kann und dass die in der fehlgeschlagenen Antwort enthaltenen Informationen höchstwahrscheinlich völlig anders sind als die reguläre Antwort.

Für #2 wollen wir uns mit einem großen Problem befassen, das in beliebten NPM-Paketen wie ky und axios auftritt.

Das Problem beim Datenabruf von Paketen

Soweit ich das beurteilen kann, mögen Leute Pakete wie ky oder axios, weil eines ihrer „Features“ darin besteht, dass sie bei nicht-OK-HTTP-Statuscodes einen Fehler auslösen. Seit wann ist das in Ordnung? Da noch nie. Aber offenbar greifen die Leute das nicht auf. Die Leute sind glücklich und zufrieden, wenn sie Fehler bei nicht OK-Antworten erhalten.

Ich stelle mir vor, dass Leute nicht-OK-Körper eingeben, wenn es Zeit zum Fangen ist. Was für ein Durcheinander, was für ein Code-Geruch!

Das ist ein Codegeruch, weil Sie try..catch-Blöcke effektiv als Verzweigungsanweisungen verwenden und try..catch nicht als Verzweigungsanweisung gedacht ist.

Aber selbst wenn Sie mit mir argumentieren würden, dass die Verzweigung in try..catch auf natürliche Weise erfolgt, gibt es einen weiteren wichtigen Grund, warum dies weiterhin schlecht ist: Wenn ein Fehler ausgegeben wird, muss die Laufzeit den Aufrufstapel abwickeln. Dies ist im Hinblick auf die CPU-Zyklen weitaus kostspieliger als eine normale Verzweigung mit einer if- oder switch-Anweisung.

Können Sie in diesem Wissen den Leistungseinbruch rechtfertigen, nur um den try..catch-Block zu missbrauchen? Ich sage nein. Ich kann mir keinen einzigen Grund vorstellen, warum die Front-End-Welt damit vollkommen zufrieden zu sein scheint.

Nachdem ich nun meine Argumentation erläutert habe, kehren wir zum Hauptthema zurück.

Das Problem im Detail

Eine HTTP-Antwort kann je nach Statuscode unterschiedliche Informationen enthalten. Beispielsweise kann ein Todo-Endpunkt wie api/todos/:id, der eine PATCH-HTTP-Anfrage empfängt, eine Antwort mit einem anderen Text zurückgeben, wenn der Statuscode der Antwort 200 ist, als wenn der Statuscode der Antwort 400 ist.

Lass es uns veranschaulichen:

// For the 200 response, a copy of the updated object:
{
    "id": 123,
    "text": "The updated text"
}

// For the 400 response, a list of validation errors:
{
    "errors": [
        "The updated text exceeds the maximum allowed number of characters."
    ]
}
Nach dem Login kopieren
Nach dem Login kopieren

Vor diesem Hintergrund kehren wir zur Problemstellung zurück: Wie kann ich eine Funktion eingeben, die diese PATCH-Anfrage ausführt, wobei TypeScript mir je nach HTTP-Statuscode beim Schreiben mitteilen kann, mit welchem ​​Body ich es zu tun habe? Code? Die Antwort: Verwenden Sie eine fließende Syntax (Builder-Syntax, verkettete Syntax), um Typen zu akkumulieren.

Die Lösung entwickeln

Beginnen wir mit der Definition eines Typs, der auf einem vorherigen Typ aufbaut:

export type AccumType<T, NewT> = T | NewT;
Nach dem Login kopieren
Nach dem Login kopieren

Super einfach: Verbinde die gegebenen Typen T und NewT zu einem neuen Typ. Verwenden Sie diesen neuen Typ erneut als T in AccumType<>, und Sie können dann einen weiteren neuen Typ akkumulieren. Das per Hand zu machen ist allerdings nicht schön. Lassen Sie uns ein weiteres Schlüsselelement für die Lösung vorstellen: Fließende Syntax.

Fließende Syntax

Angenommen ein Objekt der Klasse X, dessen Methoden sich immer selbst (oder eine Kopie von sich selbst) zurückgeben, kann man Methodenaufrufe nacheinander verketten. Dies ist eine fließende Syntax oder verkettete Syntax.

Lassen Sie uns eine einfache Klasse schreiben, die Folgendes tut:

export class NamePending<T> {
    accumulate<NewT>() {
        return this as NamePending<AccumType<T, NewT>>;
    }
}

// Now you can use it like this:
const x = new NamePending<{ a: number; }>();  // x is of type NamePending<{ a: number; }>.
const y = x.accumulate<{ b: string; }>  // y is of type NamePending<{ a: number; } | { b: string; }>.
Nach dem Login kopieren
Nach dem Login kopieren

Eureka! Wir haben die fließende Syntax und den von uns geschriebenen Typ erfolgreich kombiniert, um mit der Akkumulation von Datentypen in einem einzigen Typ zu beginnen!

Falls es nicht offensichtlich ist, können Sie die Übung fortsetzen, bis Sie die gewünschten Typen angesammelt haben (x.accumulate().accumulate()… bis Sie fertig sind).

Das ist alles gut und schön, aber dieser supereinfache Typ bindet den HTTP-Statuscode nicht an den entsprechenden Body-Typ.

Verfeinern, was wir haben

Was wir wollen, ist, TypeScript mit genügend Informationen zu versorgen, damit seine Funktion zur Typeingrenzung greift. Dazu tun wir das Notwendige, um Code zu erhalten, der für das ursprüngliche Problem relevant ist (Eingabe von Texten von HTTP-Antworten in eine per -Statuscodebasis).

Zunächst benennen Sie AccumType um und entwickeln Sie es weiter. Der folgende Code zeigt den Fortschritt in Iterationen:

// Iteration 1.
export type FetchResult<T, NewT> = T | NewT;
// Iteration 2.
export type FetchResponse<TStatus extends number, TBody> = {
    ok: boolean;
    status: TStatus;
    statusText: string;
    body: TBody
};

export type FetchResult<T, TStatus extends number, NewT> = 
    T | FetchResponse<TStatus, NewT>; //Makes sense to rename NewT to TBody.
Nach dem Login kopieren
Nach dem Login kopieren

An diesem Punkt wurde mir etwas klar: Statuscodes sind endlich: Ich kann (und habe) sie nachschlagen und Typen für sie definieren und diese Typen verwenden, um den Typparameter TStatus einzuschränken:

// Iteration 3.
export type OkStatusCode = 200 | 201 | 202 | ...;
export type ClientErrorStatusCode = 400 | 401 | 403 | ...;
export type ServerErrorStatusCode = 500 | 501 | 502 | ...;
export type StatusCode = OkStatusCode | ClientErrorStatusCode | ServerErrorStatusCode;
export type NonOkStatusCode = Exclude<StatusCode, OkStatusCode>;

export type FetchResponse<TStatus extends StatusCode, TBody> = {
    ok: TStatus extends OkStatusCode ? true : false;
    status: TStatus;
    statusText: string;
    body: TBody
};

export type FetchResult<T, TStatus extends StatusCode, TBody> = 
    T | FetchResponse<TStatus, TBody>;
Nach dem Login kopieren
Nach dem Login kopieren

Wir sind bei einer Reihe von Typen angelangt, die einfach wunderschön sind: Durch Verzweigen (Schreiben von if-Anweisungen) basierend auf Bedingungen für die ok- oder die status-Eigenschaft wird die Typeingrenzungsfunktion von TypeScript aktiviert! Wenn Sie es nicht glauben, schreiben wir den Unterrichtsteil und probieren es aus:

export class DrFetch<T> {
    for<TStatus extends StatusCode, TBody>() {
        return this as DrFetch<FetchResult<T, TStatus, TBody>>;
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Probefahren Sie dies:

// For the 200 response, a copy of the updated object:
{
    "id": 123,
    "text": "The updated text"
}

// For the 400 response, a list of validation errors:
{
    "errors": [
        "The updated text exceeds the maximum allowed number of characters."
    ]
}
Nach dem Login kopieren
Nach dem Login kopieren

Jetzt sollte klar sein, warum die Typeingrenzung die Form des Körpers beim Verzweigen basierend auf der ok-Eigenschaft der Statuseigenschaft korrekt vorhersagen kann.

Es gibt jedoch ein Problem: Die anfängliche Typisierung der Klasse, wenn sie instanziiert wird, markiert im Kommentarblock oben. Ich habe es so gelöst:

export type AccumType<T, NewT> = T | NewT;
Nach dem Login kopieren
Nach dem Login kopieren

Diese kleine Änderung macht das anfängliche Tippen praktisch überflüssig, und wir sind jetzt im Geschäft!

Jetzt können wir Code wie den folgenden schreiben und Intellisense wird 100 % genau sein:

export class NamePending<T> {
    accumulate<NewT>() {
        return this as NamePending<AccumType<T, NewT>>;
    }
}

// Now you can use it like this:
const x = new NamePending<{ a: number; }>();  // x is of type NamePending<{ a: number; }>.
const y = x.accumulate<{ b: string; }>  // y is of type NamePending<{ a: number; } | { b: string; }>.
Nach dem Login kopieren
Nach dem Login kopieren

Typeingrenzung funktioniert auch bei der Abfrage der ok-Eigenschaft.

Wenn Sie es nicht bemerkt haben: Wir konnten viel besseren Code schreiben, indem wir keine Fehler auslösten. Nach meiner beruflichen Erfahrung ist Axios falsch, Ky ist falsch und jeder andere Fetch-Helfer da draußen, der das Gleiche tut, ist falsch.

Abschluss

TypeScript macht tatsächlich Spaß. Durch die Kombination von TypeScript und fließender Syntax können wir so viele Typen wie nötig ansammeln, sodass wir vom ersten Tag an genaueren und klareren Code schreiben können, nicht erst nach mehrmaligem Debuggen. Diese Technik hat sich als erfolgreich erwiesen und kann von jedem ausprobiert werden. Installieren Sie dr-fetch und testen Sie es:

// Iteration 1.
export type FetchResult<T, NewT> = T | NewT;
// Iteration 2.
export type FetchResponse<TStatus extends number, TBody> = {
    ok: boolean;
    status: TStatus;
    statusText: string;
    body: TBody
};

export type FetchResult<T, TStatus extends number, NewT> = 
    T | FetchResponse<TStatus, NewT>; //Makes sense to rename NewT to TBody.
Nach dem Login kopieren
Nach dem Login kopieren

Ein komplexeres Paket

Ich habe auch wj-config erstellt, ein Paket, das auf die vollständige Beseitigung der veralteten .env-Dateien und dotenv abzielt. Dieses Paket verwendet ebenfalls den hier gelehrten TypeScript-Trick, verknüpft jedoch Typen mit & und nicht mit |. Wenn Sie es ausprobieren möchten, installieren Sie v3.0.0-beta.1. Allerdings sind die Eingaben deutlich komplexer. dr-fetch nach wj-config zu erstellen war ein Kinderspiel.

Lustiges Zeug: Was es da draußen gibt

Sehen wir uns einige der Fehler an, die es in Abruf-bezogenen Paketen gibt.

isomorpher Abruf

Sie können dies in der README-Datei sehen:

// Iteration 3.
export type OkStatusCode = 200 | 201 | 202 | ...;
export type ClientErrorStatusCode = 400 | 401 | 403 | ...;
export type ServerErrorStatusCode = 500 | 501 | 502 | ...;
export type StatusCode = OkStatusCode | ClientErrorStatusCode | ServerErrorStatusCode;
export type NonOkStatusCode = Exclude<StatusCode, OkStatusCode>;

export type FetchResponse<TStatus extends StatusCode, TBody> = {
    ok: TStatus extends OkStatusCode ? true : false;
    status: TStatus;
    statusText: string;
    body: TBody
};

export type FetchResult<T, TStatus extends StatusCode, TBody> = 
    T | FetchResponse<TStatus, TBody>;
Nach dem Login kopieren
Nach dem Login kopieren

„Schlechte Antwort vom Server“?? Nein. „Der Server sagt, Ihre Anfrage sei fehlerhaft.“ Ja, der Wurfteil selbst ist schrecklich.

ts-fetch

Dieser hat die richtige Idee, kann aber leider nur OK- oder Nicht-OK-Antworten eingeben (maximal 2 Typen).

ky

Eines der Pakete, das ich am meisten kritisiert habe, zeigt dieses Beispiel:

export class DrFetch<T> {
    for<TStatus extends StatusCode, TBody>() {
        return this as DrFetch<FetchResult<T, TStatus, TBody>>;
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Das würde ein sehr junger Entwickler schreiben: Nur der glückliche Weg. Die Äquivalenz laut README:

const x = new DrFetch<{}>(); // Ok, having to write an empty type is inconvenient.
const y = x
    .for<200, { a: string; }>()
    .for<400, { errors: string[]; }>()
    ;
/*
y's type:  DrFetch<{
    ok: true;
    status: 200;
    statusText: string;
    body: { a: string; };
}
| {
    ok: false;
    status: 400;
    statusText: string;
    body: { errors: string[]; };
}
| {} // <-------- WHAT IS THIS!!!???
>
*/
Nach dem Login kopieren

Der Teil des Werfens ist so schlimm: Warum solltest du dich zum Werfen verzweigen, um dich später zum Fangen zu zwingen? Für mich macht es überhaupt keinen Sinn. Auch der Text im Fehler ist irreführend: Es handelt sich nicht um einen „Abruffehler“. Der Abruf hat funktioniert. Du hast eine Antwort bekommen, nicht wahr? Es hat dir einfach nicht gefallen ... weil es nicht der glückliche Weg ist. Eine bessere Formulierung wäre „HTTP-Anfrage fehlgeschlagen:“. Was fehlgeschlagen ist, war die Anfrage selbst, nicht der Abrufvorgang.

Das obige ist der detaillierte Inhalt vonSo verwenden Sie TypeScript zum Akkumulieren von Typen: Eingabe ALLER möglichen fetch()-Ergebnisse. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage