any
typenull
& undefined
SafelyReadOnly
and ReadOnlyArray
Typesany
typePretty straightforward - any
effectively disables type checking 🙂
null
& undefined
Safelyuse union type to specify if variables could be null
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;
}
use non-strict equality check to test for 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',
};
NOTE: conflicting sub-types will result in
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.
Class properties should be readonly
if they do not need to change after initialization
class Todo {
constructor(
public title: string,
public completed: boolean,
public readonly id: string
) {
this.id = crypto.randomUUID();
}
public updateCompleted(status: boolean) {
this.completed = status;
}
}
can use readonly
modifier (or ReadonlyArray
generic type) to make arrays immutable
const immutable: readonly number[] = [1, 2, 3];
const immutable: ReadonlyArray<number> = [1, 2, 3];
this
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;
}
}
type assertion tells TypeScript to treat value as if it were a specific type
class SpecificConfig {
// ...
}
const config = state.someConfig as SpecificConfig;
type-guards offer way to protect against bugs that could be introduced by type assertions
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 {
// ...
}