javascript - Miksi TypeScript käyttää 'Like'-tyyppejä?


Miksi TypeScriptiltä löytyy tyyppi ja sitten "tyyppiin liittyvä" tyyppi? Esimerkki tästä on Promise<T> ja PromiseLike<T>.

Mitkä ovat näiden kahden tyypin erot? Milloin minun pitäisi käyttää niitä? Miksi tässä tapauksessa ei vain käytettäisi yhtä Promise-tyyppiä?

Vastaus #1

Jos katsot määritelmätiedostoja (otetaan vaikka lib.es6.d.ts), niin se on varsin suoraviivaista.

Esimerkiksi ArrayLike-liitäntä:

interface ArrayLike<T> {
    readonly length: number;
    readonly [n: number]: T;
}

on rajatumpi kuin Array-versio:

interface Array<T> {
    length: number;
    toString(): string;
    toLocaleString(): string;
    push(...items: T[]): number;
    pop(): T | undefined;
    concat(...items: T[][]): T[];
    concat(...items: (T | T[])[]): T[];
    join(separator?: string): string;
    reverse(): T[];
    shift(): T | undefined;
    slice(start?: number, end?: number): T[];
    sort(compareFn?: (a: T, b: T) => number): this;
    splice(start: number, deleteCount?: number): T[];
    splice(start: number, deleteCount: number, ...items: T[]): T[];
    unshift(...items: T[]): number;
    indexOf(searchElement: T, fromIndex?: number): number;
    lastIndexOf(searchElement: T, fromIndex?: number): number;
    
    // lots of other methods such as every, forEach, map, etc

    [n: number]: T;
}

On hyvä pitää nämä kaksi erillään, koska saatan haluta tällaisen funktion:

function getSize(arr: Array<any>): number {
    return arr.length;
}

console.log(getSize([1, 2, 3])); // works

Mutta se ei toimi tämän kanssa:

function fn() {
    console.log(getSize(arguments)); // error
}

Se johtaa tähän virheeseen:

Tyyppinen argumentti 'IArguments' ei ole yhdistettävissä tyyppiin 'any[]'.
Ominaisuutta 'push' ei ole tyypissä 'IArguments'.

Mutta molemmat toimivat, jos teen näin:

function getSize(arr: ArrayLike<any>): number {
    return arr.length;
}

(lisää ArrayLike-tietoa MDN:ssä)

Sama pätee Promise ja PromiseLike kanssa: jos rakennan kirjastoa, joka ei ota kantaa Promise-toteutuksen suhteen, sen sijaan että tekisin näin:

function doSomething(promise: Promise<any>) { ... }

Teen tämän:

function doSomething(promise: PromiseLike<any>) { ... }

Sitten, vaikka kirjastoni käyttäjä käyttäisi eri toteutusta (bluebird), se toimii silti aivan hyvin.

Jos huomaat, määritelmästä 'Promise' on tämä:

declare var Promise: PromiseConstructor;

Mikä tekee siitä hyvin spesifin, muut toteutukset saattavat omaa erilaisia ominaisuuksia, esimerkiksi erilaisen prototyypin:

interface PromiseConstructor {
    readonly prototype: Promise<any>;

    ...
}

Arvelen, että pääasiallinen syy siihen, miksi meillä on PromiseLike, on se, että useita toteutuksia oli saatavilla ennen kuin natiivi toteutus tuettiin (kuten bluebird, Promises/A+, jQuery ja muita).
Jotta TypeScript voisi toimia koodikantojen kanssa, jotka käyttävät näitä toteutuksia, täytyy olla tyyppi, joka ei ole Promise, muuten olisi paljon ristiriitoja.

Aiheeseen liittyviä artikkeleita