Použitie rekurzívnych typových aliasov v Typescripte


Mám nejaký kód, kde sa chcem pokúsiť odstrániť použitie:

let result: any;

výsledok môže byť veľký JSON vrátený z volania REST API. a nahraďte ho rekurzívnym aliasom typu Takže mám toto:

type Json = string | number | boolean | null | Array<Json> | { [key: string]: Json };

Akonáhle to urobím, môj kód potom hlási toto:

if (result.reasonCode != "8700" && result.reasonCode != "8207") {
.
.}

pretože reasonCode nebol definovaný. Tak som sa potom pokúsil pridať toto - nie som si istý, či je to správne alebo nie. Bol to len môj pokus obísť počiatočnú chybu. Dúfal som, že nebudem musieť používať rozhranie. Používam TypeScript V5.

interface JsonArray extends Array<Json> {
  reasonCode: string;
  message: string;
}

potom som zmenil výsledok na potom byť:

let result: JsonArray;

teraz nedostávam žiadne chyby pri kompilácii pre result.reasonCode.
ale teraz dostávam inú chybu

keď to urobím:

if (result === "error" {
.
.} 

s chybou, že toto porovnanie sa zdá byť neúmyselné, pretože typy 'JsonArray' a 'string' sa nepokrývajú

Aby som sa tomu vyhol, musel som potom prejsť na toto:

let result: JsonArray | "error"; 

Nemám žiadnu kontrolu nad volaním REST API, pokiaľ ide o to, čo vracia.

Zdá sa mi to všetko trochu zložité, takže hľadám nejakú radu.

Odpoveď č. 1

Váš rekurzívny typový alias definuje generickú štruktúru JSON bez konkrétnych známych polí, takže TypeScript má pravdu, keď vám hovorí, že pole reasonCode nemusí existovať v hodnote tohto typu.

Napríklad, ak je result === "error" pravdivé (čo, podľa vášho opisu, je možná situácia), potom je result typu string a prirodzene nemá pole reasonCode.

Ak by váš kód najprv zabezpečil, že hodnota, s ktorou pracujeme, nie je číslo, reťazec, boolean, null alebo pole, potom by TS mohol predpokladať, že hodnota je objekt kľúč-hodnota zodpovedajúci { [key: string]: Json }, čo by malo umožniť prístup k akýmkoľvek poliam pomocou reťazcového kľúča.

Kontrola toho je trochu zložité, pretože typeof oboch polí a null hodnôt je tiež "object". Malo by to byť niečo takéto:

if (result && !Array.isArray(result) && typeof result === 'object')

Odpoveď č. 2

Parsovanie JSON reťazca je zvyčajne bod, kde môže do vášho kódu vstúpiť neplatný kód. Napríklad, ak je tento JSON reťazec získaný z LocalStorage, je možné, že ho používateľ priamo upravil. Bez správneho overovania typov to môže viesť k neošetreným chybám.

Existuje niekoľko spôsobov, ako riešiť toto potenciálne riziko, a ktorý z nich funguje najlepšie, sa bude líšiť od prípadu k prípadu.

Napríklad, ak môžete byť veľmi istí, že viete, ako bude vyzerať váš analyzovaný JSON objekt, mohli by ste zvážiť použitie typového tvrdenia, aby ste TypeScript kompilátoru povedali, aký typ objektu bol vrátený metódou JSON.parse. Toto vám neposkytne žiadnu typovú bezpečnosť - budete tvrdiť, že typ je správny - ale má to výhodu, že nevyžaduje veľa kódu a má veľmi nízke náklady na údržbu.

Alternatívne môžete tento typ previesť na unknown namiesto použitia predvoleného správania TypeScriptu, ktorý by vrátil any. Pri tomto prístupe bude TypeScript vyžadovať, aby ste typ zúžili pred jeho použitím.


Ako krátka odbočka, existuje balík npm s názvom ts-reset, ktorý upravuje niekoľko zabudovaných typov TypeScriptu, a jednou z jeho zmien je, že JSON.parse vracia unknown pre lepšiu typovú bezpečnosť.


Existuje niekoľko spôsobov, ako spravovať tento druh kontroly typu. Ešte raz, ten najlepší pre vás bude závisieť od vášho použitia.

Najzrejmejším prístupom je napísať si kontrolu typu sám. To môže fungovať dobre pri veľmi jednoduchých typoch, ale prináša to určité riziko a náklady na údržbu. Napríklad:

type Result = 'error' | {
    reasonCode: string;
    message: string;
}

function isResult(value: unknown): value is Result {
    if (value === 'error') {
        return true;
    }

    if (!(
        typeof value === 'object' &&
        value !== null
    )) {
        return false;
    }

    if (!(
        'reasonCode' in value &&
        typeof value.reasonCode === 'string'
    )) {
        return false;
    }

    if (!(
        'message' in value &&
        typeof value.message === 'string'
    )) {
        return false;
    }

    return true;
}

// ...

declare const resultJSON: string;

const result = JSON.parse(resultJSON) as unknown;

if (!isResult(result)) {
    throw new Error('Handle this error somehow');
}

result; // <- Result

TypeScript ihrisko

Ako si určite viete všimnúť, zložitosť tejto vlastnej funkcie stráženia typu bude veľmi rýchlo rásť pri používaní zložitejších typov. Je tiež možné urobiť chyby v kontrolách, ktoré vykonávate, ale TypeScript vám verí, pokiaľ ide o časť definície typu vašej funkcie value is Result.

Ďalším prístupom, ktorý by ste k tomuto problému mohli zvoliť, a ktorý funguje lepšie pri zložitejších typoch alebo typoch, ktoré môžu byť ešte v pohybe (čo sa často stáva počas vývoja), je použitie knižnice, ktorá umožňuje definovať schému typu, ktorá môže byť použitá na generovanie typov TypeScript a funkcie na kontrolu typu, ktorá môže byť spustená v JavaScripte.

Existuje niekoľko dobre zavedených knižníc, ktoré vyplnia túto medzeru. Osobne sa mi veľmi páči Zod, ale tu je niekoľko ďalších:

  • Ajv
  • Joi
  • myzod
  • Áno

Tu je príklad, ako môžete použiť Zod:

import { z } from 'zod';

const resultSchema =
    z.literal('error')
    .or(z.object({
        reasonCode: z.string(),
        message: z.string(),
    }));

// ...

declare const resultJSON: string;

const parsedResult = JSON.parse(resultJSON) as unknown;

// Will throw an error if `parsedResult` doesn't match the schema
// Otherwise, returns a deep copy with a well-defined type
const result = resultSchema.parse(parsedResult);

result; // <- 'error' | { reasonCode: string; message: string; }

TypeScript ihrisko

Ako vidíte, toto vyžaduje menej kódu než písanie vlastnej funkcie na ochranu typu, ale zahŕňa to aj používanie inej (a možno menej známej) syntaxe na definovanie vášho typu.

Každý z týchto prístupov má rôzne náklady a prínosy, takže nemôžem jednoznačne povedať, ktorý je pre vás najlepší. Ale dúfam, že som vám aspoň poskytol niekoľko užitočných možností.

Súvisiace články