any
typenull
& undefined
SafelyReadOnly
and ReadOnlyArray
Typesany
typePretty straightforward - any
effectively disables type checking 🙂
null
& undefined
Safelynull
and/or undefined
let e: string | undefined | null;
undefined
type is sometimes inferred (such as for optional parameters)function echo(param?: string) { // <- type will be `string | undefined`
return param;
}
null
and undefined
function echo(param?: string): string {
if (param != null) return param;
return '';
}
const arr: string[] = ['a', 'b'];
arr.forEach((str: string) => { // <- could annotate callback parameter
// do something with str...
});
arr.forEach((str) => { // <- still get full type-safety without annotation
// do something with str...
});
interface Product {
sku: string;
description: string;
}
interface Shippable {
destination: string;
weight: string;
}
type ShippableProduct = Product & Shippable; // <- name is intersection of names of types
let myProduct: ShippableProduct = {
sku: 'abc',
description: 'a product',
destination: 'somewhere',
weight: '1kg',
};
never
interface Product {
sku: string;
description: string;
}
interface Shippable {
destination: string;
weight: string;
}
interface Returnable {
returnId: string;
weight: number; // <- this conflicts with `weight: string` from `Shippable`!
}
type ShippableReturnableProduct = Product & Shippable & Returnable; // <- TypeScript will let us create this union without any warnings
let product1: ShippableReturnableProduct = {
sku: '123abc',
description: '',
destination: '',
returnId: 'abc123',
weight: '1kg', // <- will throw "Error: type 'string' is not assignable to type 'never'."
}
// Numeric enum with auto-initializing zero-based indices
enum Days {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
// Numeric enum with explicitly assigned indices
enum Days {
Monday = 0,
Tuesday = 1,
Wednesday = 2,
Thursday = 3,
Friday = 4,
Saturday = 5,
Sunday = 6,
}
// String enum
enum Days {
Monday = 'monday',
Tuesday = 'tuesday',
Wednesday = 'wednesday',
Thursday = 'thursday',
Friday = 'friday',
Saturday = 'saturday',
Sunday = 'sunday',
}
Enums similar in some ways to union types and can sometimes be used in similar contexts
type Strs = 'a' | 'b' | 'c';
enum Str {
A = 'a',
B = 'b',
C = 'c',
}
// Example 1
type Strs = 'a' | 'b' | 'c';
function checkString(str: Strs) {
switch (str) {
case 'a':
// etc
}
}
// Example 2
enum Str {
A = 'a',
B = 'b',
C = 'c',
}
function checkString(str: Str) {
switch (str) {
case Str.A:
// etc
}
}
Tuples are fixed-length arrays, where each element has specific type
type Coords = [number, number];
type CoordsAndName = [
number,
number,
string,
];
// Can label elements in tuple to better communicate meaning of each element
type Coords = [
latitude: number,
longitude: number,
];
readonly
and ReadonlyArray
TypesSince class properties/objects/arrays are mutable by default, can use readonly
and ReadonlyArray
types to specify whether certain things should be immutable.
readonly
if they do not need to change after initializationclass Todo {
constructor(
public title: string,
public completed: boolean,
public readonly id: string
) {
this.id = crypto.randomUUID();
}
public updateCompleted(status: boolean) {
this.completed = status;
}
}
readonly
modifier (or ReadonlyArray
generic type) to make arrays immutableconst immutable: readonly number[] = [1, 2, 3];
const immutable: ReadonlyArray<number> = [1, 2, 3];
this
Fluent API
Polymorphic this
this
object can refer to different things in different contextsthis
polymorphicclass Calculator {
private result: number = 0;
public add(num: number): this {
this.result += num;
return this;
}
public subtract(num: number): this {
this.result -= num;
return this;
}
public multiply(num: number): this {
this.result *= num;
return this;
}
public divide(num: number): this {
this.result /= num;
return this;
}
public reportResult(num: number): number {
return this.result;
}
}
class SpecificConfig {
// ...
}
const config = state.someConfig as SpecificConfig;
class SpecificConfig {
// ...
}
function isSpecificType(config: any): config is SpecificConfig {
return config instanceof SpecificConfig;
}
default
case to handle scenarios that can/should never happenenum Themes {
regular = 'regular',
dark = 'dark',
highContrast = 'highcontrast',
};
const unhandled = (_: never): never => _;
function switchTheme(theme: Themes) {
switch (theme) {
case Themes.regular:
// handle regular theme
break;
case Themes.dark:
// handle dark theme
break;
default:
/**
*~ This will throw a compiler error that "Argument of type 'Themes' is
*~ not assignable to parameter of type 'never'.
*~ Additionally in your IDE, the type hint will specifically draw attention
*~ to the unused property from the `Themes` enum, in this case:
*~ `(parameter) theme: Themes.highContrast`
*/
return unhandled(theme);
}
}
Partial
, Required
, Record
, Pick
, Omit
, etc.
type NewType = TestType extends ReferenceType ? OutputTypeA : OutputTypeB;
/**
*~ If `TestType` is assignable to `ReferenceType`
*~ evaluates as `OutputTypeA`
*~ Else
*~ evaluates as `OutputTypeB`
*/
/*------------- Practical example -------------*/
/* Before refactor */
type Log =
| { type: 'INFO'; message: string; }
| { type: 'ERROR'; message: string; error: Error; };
function logIt(log: Log): void {
switch (log.type) {
case 'INFO':
console.log(log.message);
break;
case 'ERROR':
console.log(log.message, log.error.stack);
}
}
logIt({ type: 'INFO', message: 'I love lamp' });
/* After refactor */
type Log =
| { type: 'INFO'; message: string; }
| { type: 'ERROR'; message: string; error: Error; };
type LogType = Log['type'];
type ExtractNonTypeParams<L, T> = L extends { type: T } ? Omit<L, 'type'> : never;
function logIt<T extends LogType>(
logType: T,
args: ExtractNonTypeParams<Log, T>
): void {
switch (logType) {
case 'INFO':
console.log(args.message);
break;
case 'ERROR':
console.log(args.message, (args as ExtractNonTypeParams<Log, "ERROR">).error.stack);
}
}
experimentalDecorators
optionfunction instrument<T extends { new (..args: any[]): object }>(target: T) {
const originalConstructor = target.prototype.constructor;
for (const key of Object.getOwnPropertyNames(originalConstructor.prototype)) {
const descriptor = Object.getOwnPropertyDescriptor(originalConstructor.prototype, key);
if (descriptor != null && typeof descriptor.value === 'function') {
const originalFunction = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling method '${key}' with arguments: ${args.join(', ')}`);
const result = originalFunction.apply(this, args);
if (result != null) {
console.log(`Method '${key}' returned: ${result}`);
}
return result;
};
Object.defineProperty(originalConstructor, key, descriptor);
}
}
}
@instrument
class Todo {
// ...
}