typescript - Kuinka määrität, että luokan ominaisuus on kokonaisluku?


Kokeilen TypeScriptiä, ja luokan tekemisen prosessissa, jossa on ID-kenttä, jonka pitäisi olla integer, olen hieman hämmentynyt.

Ensinnäkin, Visual Studio 2012:ssa TypeScript-laajennuksen kanssa näen int intelliSense-tyyppiluettelossa. Mutta saan käännösvirheen, joka sanoo:

nimi 'int' ei ole olemassa nykyisessä näkyvyydessä.

Kävin läpi kielispesifikaatiot ja näen vain seuraavat primitiiviset tyypit: number, string, boolean, null ja undefined. Ei integer-tyyppiä.

Joten minulle jää kaksi kysymystä:

  1. Miten minun tulisi ilmaista luokkani käyttäjille, että tietty kenttä ei ole pelkkä numero, vaan kokonaisluku (eikä koskaan liukulukua tai desimaalilukua)?

  2. Miksi näen int intellisense-listassa, jos se ei ole kelvollinen tyyppi?

Päivitys: Kaikki tähän mennessä saamani vastaukset koskevat sitä, että JavaScript ei omaa int-tyyppiä ja että olisi vaikeaa pakottaa int-tyyppiä suoritusaikana... Tiedän kaiken sen. Kysyn, onko olemassa TypeScript-tapa antaa luokkani käyttäjille merkintä siitä, että tämän kentän tulisi olla integer. Ehkä kommentti jossain erityisessä muodossa?

Vastaus #1

  1. Mielestäni ei ole suoraa tapaa määrittää, onko luku kokonaisluku vai liukuluku. TypeScriptin määrittelyosion 3.2.1 kohdassa voimme nähdä:

    "...Number-primitiivityyppi vastaa samannimistä JavaScriptin primitiivityyppiä ja edustaa kaksinkertaisen tarkkuuden 64-bittisen IEEE 754 -liukulukuarvon arvoja..."

  2. Luulen, että int on bugi Visual Studion IntelliSenseissä. Oikea on number.

Vastaus #2

TypeScript on JavaScriptin yläjoukko, jolla ei ole int-käsitettä. Sillä on vain numerokäsite, jolla on liukulukuarvo.

Yleisesti ottaen se työmäärä, jonka kääntäjän pitäisi tehdä pakottaakseen TypeScriptin int-tyyppi käyttämään vain kokonaislukuja, voisi olla mahdollisesti valtava, ja joissain tapauksissa ei silti olisi mahdollista varmistaa käännösaikana, että vain kokonaislukuja olisi määritetty, minkä takia ei ole mahdollista lisätä luotettavasti int TypeScriptiin.

Kun saat aluksi intelliSensen Visual Studiossa, työkalun ei ole mahdollista määrittää, mitä tarjota, joten saat kaiken, mukaan lukien int - mutta kun käsittelet jotain tunnetun tyyppistä, saat järkevän intelliSensen.

Esimerkkejä

var myInt: number;
var myString: string;

myInt. // toExponential, toFixed, toPrecision, toString
myString. // charAt, charCodeAt, concat, indexOf, lastIndexOf, length and many more...

Vastaus #3

TypeScriptissä voit likimääräisesti mallintaa sitä, mitä joskus kutsutaan läpinäkymättömäksi tyypiksi, käyttämällä merkkiä.

// Helper for generating Opaque types.
type Opaque<T, K> = T & { __opaque__: K };

// 2 opaque types created with the helper
type Int = Opaque<number, 'Int'>;
type ID = Opaque<number, 'ID'>;

// using our types to differentiate our properties even at runtime
// they are still just numbers
class Foo {
    someId: ID;
    someInt: Int;
}

let foo = new Foo();

// compiler won't let you do this due to or markers
foo.someId = 2;
foo.someInt = 1;

// when assigning, you have to cast to the specific type
// NOTE: This is not completely type safe as you can trick the compiler 
// with something like foo.someId = 1.45 as ID and it won't complain.
foo.someId = 2 as ID;
foo.someInt = 1 as Int;

// you can still consume as numbers
let sum: number = foo.someId + foo.someInt;

Tämä antaa sinun olla selkeämpi koodissasi sen suhteen, millaisia tyyppejä ominaisuutesi odottavat, eikä kääntäjä salli sinun antaa primitiiviarvoa ilman tyyppimuunnosta. Tämä ei tuota mitään ylimääräistä .js-tulostetta, ja voit silti käyttää arvoja sellaisina tyyppeinä kuin ne ovat. Tässä esimerkissä käytän lukuja, mutta voit käyttää myös merkkijonoja ja muita tyyppejä.

Voit silti huijata kääntäjää hyväksymään jotain, mikä ei ole Int tai Id tässä esimerkissä, mutta sen pitäisi herättää huomiota, jos yrität esimerkiksi määrittää 1.45 Intiksi tai jotain sellaista. Sinulla on myös mahdollisuus luoda apufunktioita, joita käytät arvojesi luomiseen tarjoamaan ajonaikaista validointia.

On useita eri tapoja, joilla voit luoda "merkittyjä" tyyppejä. Tässä on hyvä artikkeli: https://michalzalecki.com/nominal-typing-in-typescript/

Vastaus #4

Tämä on minulle Googlen tärkein tulos, joten ajattelin, että minun pitäisi tarjota löytämäni ratkaisut.

Käyttäen bigint

Nyt kun vuosi on 2020 ja bigint on hyväksytty, se ansaitsee maininnan. Voit yksinkertaisesti tehdä alla olevan. Huomaa, että bigintit vaikuttavat suorituskykyyn enemmän verrattuna numberiin.

const myNumber: bigint = 10n

Nominaalityypin/merkityn tyypin/läpinäkymättömän tyypin käyttäminen

Vaihtoehtona on käyttää nimellistä tyyppiä, mutta se on mahdollisesti vähemmän ergonominen, enkä ole varma, onko se yhtään nopeampi kuin bigint, mutta malli yleistyy mihin tahansa tyyppiin, ei vain number. TypeScriptilla ei ole "ensisijaisen luokan" tukea tälle, joten sinun täytyy tehdä kekseliäs kiertotapa. Tähän on olemassa kirjasto nimeltä newtype-ts, joka sisältää yleisiä tyyppejä kuten Integer, joten saatat haluta vain käyttää sitä, mutta selitän toimintaperiaatteen alla.

Aloittaaksemme määrittelemme integer-tyypin.

const TAG = Symbol()
type integer = number & { readonly [TAG]: unique symbol }

TAG varmistaa, että meillä on ainutlaatuinen arvo, jotta emme vahingossa luo objektia samalla avaimella, ja teemme kentästä myös ainutlaatuisen symbolin samasta syystä. Nyt kokonaisluvullasi ei oikeastaan ole tätä objektikenttää, mutta se on ihan ok.

Tämän avulla voit silti lisätä integer number:iin käyttäen +. Ei hyvä. Joten voit varmistaa tyyppiturvallisuuden argumenteille täällä muokkaamalla tyyppijärjestelmää funktion avulla. Aion vain kutsua sitä guardiksi, ja kuten näet, se ei ole spesifinen integer:eille - voit tehdä lisää läpinäkymättömiä tyyppejä ja käyttää tätä uudelleen.

type guard = <A>(f: (...ns: Array<A>) => A, ...ns: Array<A>) => A
const guard: guard = (f, ...ns) => f(...ns)

Jos yrität kutsua sitä numerolla

const bad: integer = guard((a, b) => a + b as integer, myCoolInteger, 10)

saat virheen kuten alla

Argument of type '10' is not assignable to parameter of type 'integer'.
  Type '10' is not assignable to type '{ readonly [TAG]: unique symbol; }'.(2345)

Huomaa, ettet tässä pakota palautustyyppiä (koska sinun täytyy käyttää as integer) ja jotkin operaattorit, kuten /, palauttavat liukulukuarvoja, joten haluat todennäköisesti silti tehdä ajonaikaisia tarkistuksia tai lisätä Math.round guard-funktion erikoistettuun versioon, mutta tämä ainakin varmistaa, ettet yritä käyttää kahta erilaista numeerista tyyppiä yhdessä - kuvittele, että sinulla on GBP ja USD ja yrität lisätä ne, se ei todennäköisesti ole se, mitä tarkoitit.

Vastaus #5

TypeScriptissä ei ole integer- tai float-tyyppiä, vaan vain number-tyyppi, kuten JavaScriptissä. Mutta jos haluat kertoa ohjelmoijalle, että odotat integer-tyyppiä, voit yrittää käyttää tyyppialiasia kuten

type integer = number;
type float = number;

// example:
function setInt(id: integer) {}

mutta tämä on silti number-tyyppiä ja voit saada float.

Osa kuvauksesta dokumentaatiosta:
„Aliasing ei itse asiassa luo uutta tyyppiä - se luo uuden nimen viitatakseen tähän tyyppiin. Alkeistyypin aliasointi ei ole kovin hyödyllistä, vaikka sitä voidaan käyttää dokumentaation muodossa.”

Aiheeseen liittyviä artikkeleita