typescript - Miksi ts-toolbelt-kirjasto käyttää "O extends unknown" -lausetta


Opiskelen ts-toolbelt-kirjaston lähdekoodia. Ja usein törmään tähän ilmaisuun O extends unknown. Minun mielestäni se ei lisää mitään toiminnallisuutta.

Ja niin minä mietin, mihin sitä käytetään?

/**
 * @hidden
 */
export type _UnionOf<O extends object> =
    O[keyof O]

/**
 * Transform an [[Object]] into an [[Union]]
 * @param O to transform
 * @returns [[Any]]
 * @example
 * ```ts
 * ```
 */
export type UnionOf<O extends object> =
    O extends unknown
    ? _UnionOf<O>
    : never

Jostain syystä _UnionOf-tyypin viemisen sijaan sitä edeltää lauseke O extends unknown

Vastaus #1

Oletettavasti kirjaston kirjoittajat päättivät, että UnionOf<A | B | C> pitäisi tuottaa saman tuloksen kuin UnionOf<A> | UnionOf<B> | UnionOf<C>, ja _UnionOf:n määritelmä ei tehnyt niin. O extends unknown ? ... : never -tarkistus, joka harhaanjohtavasti näyttää siltä, ettei se tee mitään, aiheuttaa tämän tapahtumaan.

Lausekkeet, jotka näyttävät tekevän mitään, mutta jakautuvat itse asiassa liitoihin, kun T on geneerinen tyyppiparametri:

  • T extends unknown ? ... : never
  • T extends any ? ... : never
  • T extends T ? ... : never

Jos T on tyyppiparametri, kuten geneerissä funktiossa function foo<T>(/*...*/): void, geneerisessä tyyppialiasissa type Foo<T>=/*...*/ tai geneerisessä rajapinnassa interface Foo<T> {/*...*/}, niin tyyppi muodossa T extends XXX ? YYY : ZZZ on jakautuva ehdollinen tyyppi.

Se jakaa ehdollisen tarkistuksen liittojen kesken T:ssä. Kun T määritellään jollain tietyllä tyypillä, kääntäjä jakaa kyseisen tyypin sen liittojäseniksi, arvioi tarkistuksen jokaiselle tällaiselle jäsenelle ja yhdistää sitten tulokset takaisin uuteen liittoon. Joten jos F<T> jakautuu liittojen kesken, niin F<A | B | C> on sama kuin F<A> | F<B> | F<C>.


Kaikki tyypin funktiot eivät ole distributiivisia liitoksissa. Esimerkiksi keyof-operaattori ei muunna syötteiden liitoksia tulosten liitoksiksi:

type KeyofA = keyof {a: string}; // "a"
type KeyofB = keyof {b: number}; // "b"
type KeyofAorB = keyof ({ a: string } | { b: number }); // never

Samoin, _UnionOf ei ole distributiivinen unionien yli (liittyen siihen, että se käyttää määritelmässään keyof-avainsanaa):

type Oops = _UnionOf<{ a: string } | { b: number }>
// never

Jos sinulla on tyyppifunktio, joka ei ole unionien suhteen jakautuva, ja haluat sen olevan, voit kietoa sen jakautuvaan ehdolliseen tyyppiin:

type DistribKeyof<T> = T extends unknown ? keyof T : never;

type UnionKeyofAorB = DistribKeyof<{ a: string } | { b: number }>; // "a" | "b"

Ja siksi UnionOf on distributiivinen yhdistymien yli:

type Correct = UnionOf<{ a: string } | { b: number }>
// string | number

Leikkikenttälinkki koodiin

Vastaus #2

`O extends unknown ? _UnionOf<O> : never` on ehdotyyppi; tässä sitä käytetään jakautuvana ehdotyyppinä. Ilman ehdotyyppiä TypeScript kaventaisi tyypin yhdeksi, joka pätee kaikkiin mahdollisiin yhdistearvoihin (usein never). Jakautuvien ehdotyyppien avulla se soveltuu kuhunkin mahdolliseen yhdistearvoon. Vaikka jakautuvien ehdotyyppien ehdollista osaa voitaisiin käyttää mahdollisten yhdistearvojen suodattamiseen, tässä se ei ole tarpeen, joten näet usein arvoja kuten T extends any (kuten käsikirjassa) tai T extends unknown täällä.

Mitä tulee niiden väliseen eroon, dragomirtitian kuvaa sitä Typescriptin gitter.imissä:

any poistaa joskus tietoa tyyppiparametrista. Tähän liittyen oli bugi, jonka itse raportoin: microsoft/TypeScript#30569. Sen pitäisi toimia nyt hyvin, mutta suosittelisin silti jotain kahta muuta tapaa [huom: T extends unknown tai T extends T]. Mikä tahansa extends-lauseke ehdollisessa tyypissä lisää tietoa T:stä, extends any voi hämmentää kääntäjää T:n suhteen.

Katso myös:

  • Mitä tarkoittaa, että tyyppi jakautuu liitoille?
  • Typescriptin jakautuvat ehdolliset tyypit

Aiheeseen liittyviä artikkeleita