null
And undefined
TypeScript also has a special syntax for removing null
and undefined
from a type without doing any explicit checking. Writing !
after any expression is effectively a type assertion that the value isn’t null
or undefined
:
Optional Properties
TypeScript: Documentation - Optional Properties
Object types can also specify that some or all of their properties are optional. To do this, add a ?
after the property name:
function printName(obj: { first: string; last?: string }) {
// ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
// last is possibly 'undefined'.
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
// A safe alternative using modern JavaScript syntax:
console.log(obj.last?.toUpperCase());
}
Non-null Assertion Operator (Postfix!
)
a special syntax for removing null
and undefined
from a type without doing any explicit checking. Writing !
after any expression is effectively a type assertion that the value isn’t null
or undefined
:
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
readonly
Properties
A property marked as readonly
can’t be written to during type-checking.
Using the readonly
modifier doesn’t necessarily imply that a value is totally immutable - or in other words, that its internal contents can’t be changed. It just means the property itself can’t be re-written to.
interface Home {
readonly resident: { name: string; age: number };
}
function visitForBirthday(home: Home) {
// We can read and update properties from 'home.resident'.
console.log(`Happy birthday ${home.resident.name}!`);
home.resident.age++;
}
function evict(home: Home) {
// But we can't write to the 'resident' property itself on a 'Home'.
home.resident = {
// Cannot assign to 'resident' because it is a read-only property.
name: "Victor the Evictor",
age: 42,
};
}
Mapped Types
TypeScript: Documentation - Mapped Types
There are two additional modifiers which can be applied during mapping: readonly
and ?
which affect mutability and optionality respectively.
You can remove or add these modifiers by prefixing with -
or +
. If you don’t add a prefix, then +
is assumed.
// Removes 'readonly' attributes from a type's properties
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
// Removes 'optional' attributes from a type's properties
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type User = Concrete<MaybeUser>;
Index Signatures
Union Types
You can actually use a type alias to give a name to any type at all, not just an object type. For example, a type alias can name a union type:
type ID = number | string;
Type Assertions
If you’re using document.getElementById
, TypeScript only knows that this will return some kind of HTMLElement
, but you might know that your page will always have an HTMLCanvasElement
with a given ID.
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
Type Literals
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
The as const
suffix acts like const
but for the type system, ensuring that all properties are assigned the literal type instead of a more general version like string
or number
.
Type Guards
JavaScript supports a typeof
operator which can give very basic information about the type of values we have at runtime. TypeScript expects this to return a certain set of strings:
"string"
"number"
"bigint"
"boolean"
"symbol"
"undefined"
"object"
"function"
instanceof
is also a type guard, and TypeScript narrows in branches guarded by instanceof
s.
Type Predicate
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
A predicate takes the form x is String
, where x
must be the name of a parameter from the current function signature.
The return type of the function is annotated as x is string
, which asserts that x
is of type string
if the function returns true
.