typförsäkran - Vad betyder "as const" i TypeScript och vad är dess användningsfall?
Jag är förvirrad över as const-typen. Jag har kollat några dokument och videor men förstod det inte helt.
Min oro är vad as const betyder i koden nedan och vilken nytta det har att använda det?
const args = [8, 5] as const;
const angle = Math.atan2(...args);
console.log(angle);
Svar #1
Detta är känt som en const-assertion. En const-assertion berättar för kompilatorn att härleda den snävaste* eller mest specifika typen som den kan för ett uttryck. Om du utelämnar den kommer kompilatorn att använda sitt standardbeteende för typinferens, vilket möjligen resulterar i en bredare eller mer generell typ.
Notera att det kallas en "assertion" och inte en "cast". Termen "cast" bör generellt undvikas i TypeScript; när folk säger "cast" antyder de ofta någon form av effekt som kan observeras vid körning, men TypeScripts typ-system, inklusive type assertions och const assertions, tas helt bort från den genererade JavaScript-koden. Så det finns absolut ingen skillnad vid körning mellan ett program som använder as const och ett som inte gör det.
Vid kompileringstid finns det dock en märkbar skillnad. Låt oss se vad som händer när du utelämnar as const i exemplet ovan:
const args = [8, 5];
// const args: number[]
const angle = Math.atan2(...args); // error! Expected 2 arguments, but got 0 or more.
console.log(angle);
Kompilatorn ser const args=[8, 5]; och härleder typen number[]. Det är en muterbar array med noll eller fler element av typen number. Kompilatorn har ingen aning om hur många eller vilka element som finns. En sådan härledning är generellt sett rimlig; ofta är det meningen att arrayens innehåll ska modifieras på något sätt. Om någon vill skriva args.push(17) eller args[0]++, kommer de vara nöjda med en typ av number[].
Tyvärr resulterar nästa rad, Math.atan2(...args), i ett fel. Math.atan2()-funktionen kräver exakt två numeriska argument. Men allt kompilatorn vet om args är att det är en array av tal. Den har helt glömt att det finns två element, och därför klagar kompilatorn på att du anropar Math.atan2() med "0 eller fler" argument när den vill ha exakt två.
Jämför det med koden med as const:
const args = [8, 5] as const;
// const args: readonly [8, 5]
const angle = Math.atan2(...args); // okay
console.log(angle);
Nu härleder kompilatorn att args är av typen readonly [8, 5]... en readonly-tuple vars värden är exakt siffrorna 8 och 5 i den ordningen. Specifikt är args.length känt av kompilatorn att vara exakt 2.
Och detta är tillräckligt för att nästa rad med Math.atan2() ska fungera. Kompilatorn vet att Math.atan2(...args) är samma som Math.atan2(8, 5), vilket är ett giltigt anrop.
Och igen: vid körning finns det ingen som helst skillnad. Båda versionerna loggar 1.0121970114513341 till konsolen. Men const-påståenden, liksom resten av det statiska typ-systemet, är inte avsedda att ha effekter vid körning. Istället låter de kompilatorn veta mer om avsikten med koden och kan mer exakt skilja mellan korrekt kod och buggar.
Playground-länk till kod
* Detta är inte strikt sant för array- och tuplatyper; en readonly array eller tupla är tekniskt sett bredare än en muterbar version. En muterbar array betraktas som en subtyp av en readonly array; den förstnämnda är inte känd för att ha muteringsmetoder som push() medan den sistnämnda har det.
Svar #2
Med enkla ord låter det dig skapa helt skrivskyddade objekt, detta är känt som const-assertion, i din kod betyder as const att värdena på arrayens positioner är readonly, här är ett exempel på hur det fungerar:
const args = [8, 5] as const;
args[0] = 3; // throws "Cannot assign to '0' because it is a read-only
args.push(3); // throws "Property 'push' does not exist on type 'readonly [8, 5]'"
Du kan se vid det sista kastade felet att args=[8, 5] as const tolkas som args: readonly [8, 5], det är för att den första deklarationen är ekvivalent med en readonly-tuple.
Tupler har många användningsområden, några exempel är: spridda parametrar, destruktiva värden, etc. En generell fördel är readonly-beteendet som läggs till alla dess objektattribut:
const args = [8, 5];
// Without `as const` assert; `args` stills a constant, but you can modify its attributes
args[0] = 3; // -- WORKS ✓
args.push(3); // -- WORKS ✓
// You are only prevented from assigning values directly to your variable
args = 7; // -- THROWS ERROR ✖
Det finns några undantag för att påståendena är 'fullständigt skrivskyddade', du kan kolla dem här. För mer detaljer, här är en lista över andra relaterade frågor/svar som hjälpte mig att förstå konstantpåståendet:
- Hur deklarerar jag en skrivskyddad array-tupel i TypeScript?
- Finns det någon skillnad mellan typförklaringar och den nyare
as-operatorn i TypeScript? - Typescript skrivskyddade tupler
Svar #3
Om du skulle skriva const args=[8, 5], skulle ingenting hindra dig från att sedan också skriva args[0]=23 eller args.push(30) eller något annat för att ändra den arrayen. Allt du har gjort är att tala om för TS/JS att variabeln med namnet args pekar på just den här arrayen, så du kan inte ändra vad den refererar till (t.ex. kan du inte göra args="något annat"). Du kan ändra arrayen, men du kan inte ändra vad variabeln pekar på.
Å andra sidan gör tillägget av as const till en deklaration den nu verkligen konstant. Hela grejen är skrivskyddad, så du kan inte ändra arrayen alls.
För att förtydliga, som påpekats i kommentarerna:
“really makes it constant” skulle kunna antyda att det finns någon körtidseffekt när det inte finns någon. Vid körtid kommer args.push(30) fortfarande att ändra arrayen. Allt som as const gör är att TypeScript-kompilatorn kommer att klaga om den ser att du gör det. - jcalz
as const påverkar bara kompilatorn, och det finns ett undantag från dess skrivskyddade effekt (se kommentarerna). Men i allmänhet är det fortfarande den största användningsskillnaden mellan const och as const. Den ena används för att göra en referens oföränderlig och den andra används för att göra det som refereras till oföränderligt.
Svar #4
as const när det används på ett objekt eller en array gör dem oföränderliga (d.v.s. gör dem skrivskyddade). För andra litteraler förhindrar det typvidgning.
const args = [8, 5] as const;
args[0] = 10; ❌ Cannot assign to '0' because it is a read-only property.
Få andra fördelar :
- Du kan fånga buggar vid kompilering utan att köra programmet om du inte kastar till en annan typ.
- Kompilatorn kommer inte att tillåta dig att tilldela om egenskaper hos nästlade objekt.
Svar #5
Det är en const-assertion. Här är ett användbart inlägg om dem, och här är dokumentationen.
När vi konstruerar nya litterala uttryck med const-assertioner kan vi signalera till språket att
- inga bokstavliga typer i det uttrycket bör utvidgas (t.ex. ingen övergång från "hello" till string)
- objektliteraler får skrivskyddade egenskaper
- matrisvärden blir skrivskyddade tupler
Med const args=[8, 5] as const; gäller den tredje punkten, och tsc kommer att förstå det som:
// Type: readonly [8, 5]
const args = [8, 5] as const;
// Ok
args[0];
args[1];
// Error: Tuple type 'readonly [8, 5]' of length '2' has no element at index '2'.
args[2];
Utan påståendet:
// Type: number[]
const args = [8, 5];
// Ok
args[0];
args[1];
// Also Ok.
args[2];
