Type assertions
Sometimes you know more than TypeScript does. For example, document.getElementById returns HTMLElement | null, but you know the element is a canvas:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
Assertions are erased at compile time : no runtime check. If you are wrong, you can get runtime errors.
Angle-bracket form is equivalent (avoid in .tsx files):
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
TypeScript only allows assertions between related types. Impossible coercions are blocked:
const x = "hello" as number;
// Error: Conversion of type 'string' to type 'number' may be a mistake...
Force it only through unknown or any when you truly mean it:
const a = expr as unknown as T;
Literal types
You can use specific strings and numbers as types:
let x: "hello" = "hello";
x = "hello"; // OK
x = "howdy"; // Error
Alone that is rarely useful. Combined into a union, literals model allowed values:
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// Error: Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
Numeric literals work too:
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
Mix with object types:
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
boolean is essentially the union true | false.
Literal inference and as const
Object properties are often widened to string / number:
declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// Error: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
Fixes:
const req = { url: "https://example.com", method: "GET" as "GET" };
// or
handleRequest(req.url, req.method as "GET");
// or freeze the whole object as literals:
const req2 = { url: "https://example.com", method: "GET" } as const;
handleRequest(req2.url, req2.method);
as const makes TypeScript treat properties as their literal types.
null and undefined
JavaScript uses both for "missing" values. With strictNullChecks on (recommended), you must handle them:
function doSomething(x: string | null) {
if (x === null) {
// handle missing value
} else {
console.log("Hello, " + x.toUpperCase());
}
}
Non-null assertion (!)
! asserts a value is not null or undefined:
function liveDangerously(x?: number | null) {
console.log(x!.toFixed());
}
Same rules as other assertions: no runtime check. Prefer real narrowing when you can.
Enums and other primitives (brief)
- Enums : TypeScript feature for named constants; useful to know about, but often string literal unions are enough.
bigint: large integers (100norBigInt(100)).symbol: unique references viaSymbol("name").
Practice
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function request(url: string, method: HttpMethod) {
console.log(method, url);
}
const options = { url: "/api/users", method: "GET" } as const;
request(options.url, options.method);
function lengthOf(s: string | null | undefined) {
// return s.length safely
}
Implement lengthOf with narrowing (no ! unless you can justify it).