9:00am - Introductions & set-up
9:30am - TypeScript
10:00am - Types
10:45am - Casting
12:00pm - Lunch π½οΈ
11:30pm - Generics
1:00pm - Conditional Types
1:45pm - Mapped Types
3:30pm - Putting it all together!
5:00pm - Finish π
2:30pm - Helper Types
github.com/phenomnomnominal/advanced-typescript
If you don't have access, check your email for instructions.
git clone https://github.com/phenomnomnominal/advanced-typescript.git
npm i
Node.js and npm version shouldn't matter, but latest is good!
npm run test
Typed super-set of JavaScript - all valid JavaScript code is valid TypeScript
Launched by Microsoft in 2012
Aims to make it easier to write larger JavaScript applications by introducing type-safety.
Has quickly become one of the most-used and most-loved programming languages in the world!
const hello: string = 'world';
let beast: number = 666;
function add (a: number, b: number): number {
return a + b;
}
class Person {
constructor (
private _name: string
) { }
public get name (): string {
return this._name;
}
}
enum Sizes {
SMALL,
MEDIUM,
LARGE
}
Defining values with a type
Defining parameters with types
Defining return type
Defining a class of objects
With typed properties
Defining a enum
interface Set<T> {
/**
* Appends a new element with a specified value to the
* end of the Set.
*/
add(value: T): this;
clear(): void;
/**
* Removes a specified value from the Set.
* @returns Returns true if an element in the Set existed
* and has been removed, or false if the element does not
* exist.
*/
delete(value: T): boolean;
/**
* Executes a provided function once per each value in
* the Set object, in insertion order.
*/
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void;
/**
* @returns a boolean indicating whether an element with
* the specified value exists in the Set or not.
*/
has(value: T): boolean;
/**
* @returns the number of (unique) elements in Set.
*/
readonly size: number;
}
Defining a generic interface for a built-in JavaScript type
Using the generic type
Adding modifiers to properties
type Digits = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
Defining a union type for digits
type ParseSeconds<SecondsStr extends string> =
SecondsStr extends Seconds
? SecondsStr
: DateParseError<`ParseSeconds: Invalid Seconds: ${SecondsStr}`>;
type ParseMinutes<MinutesStr extends string> =
MinutesStr extends Minutes
? MinutesStr
: DateParseError<`ParseMinutes: Invalid Minutes: ${MinutesStr}`>;
type ParseHours<HourStr extends string> =
HourStr extends Hours
? HourStr
: DateParseError<`ParseHours: Invalid Hour: ${HourStr}`>;
type Hours = `0${Digits}` | `1${Digits}` | '20' | '21' | '22' | '23' | '24';
type Minutes = `${'0' | '1' | '2' | '3' | '4' | '5'}${Digits}`;
type Seconds = Minutes;
type DateParseError<Message extends string> = { error: Message };
Defining a generic error type
Defining Template Literal types for Hours, Minutes and Seconds
Defining a Conditional Type to parse time strings
// index.js
var hello = 'world';
function add (a, b) {
return a + b;
}
// index.ts
var hello = 'world';
function add (a, b) {
return a + b;
}
tsc
The TypeScript compiler (tsc
) does two main things:
// index.ts
const variable = 'whoop!';
console.log(variable * 3);
const variable: "whoop!";
The left-hand side of an arithmetic operation must be of type 'any',
'number', 'bigint', or an enum type.
tsc
TypeScript knows what a const
is so it knows that the variable will always be a string!
tsc
error message - they are often much more convoluted than this!
// index.ts
const variable: string = null;
console.log(variable.toLowerCase());
tsc
β
By default, tsc
allows any variable to acceptΒ null values!
The type-checker will not report an issue, by there could be runtime errors!
Β "Billion Dollar Mistake"
// index.ts
const variable: string = null;
console.log(variable.toLowerCase());
tsc --strict
const variable: string;
Type 'null' is not assignable to type 'string'.
In strict
Β mode the type-checker will exclude null
as a valid value
Another pretty readable error message!
// ./src/index.ts
// ./src/types.ts
// third-party-fun
// @types/third-party-fun
// tsconfig.json
tsc --noEmit
β¨β¨β¨β¨
// lib.dom.d.ts
// index.js
tsc
// index.ts
interface Set<T> {
add(value: T): this;
clear(): void;
delete(value: T): boolean;
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void;
has(value: T): boolean;
readonly size: number;
}
type Numbers = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
type DateParseError<Message extends string> = { error: Message };
type Hours = `0${Numbers}` | `1${Numbers}` | '20' | '21' | '22' | '23' | '24';
type Minutes = `${'0' | '1' | '2' | '3' | '4' | '5'}${Numbers}`;
type Seconds = Minutes;
// index.js
var hello = 'world';
function add(a, b) {
return a + b;
}
tsc --target ES5
// index.ts
const hello: string = 'world';
const add = (a: number, b: number): number => {
return a + b;
}
// index.ts
const hello: string = 'world';
const add = (a: number, b: number): number => {
return a + b;
}
// index.js
const hello = 'world';
const add = (a, b) => {
return a + b;
};
tsc --target ES2015
// index.ts
async function getFileContents (path: string): Promise<string> {
return await readFile(path, 'utf-8')
}
// index.js
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
function getFileContents(path) {
return __awaiter(this, void 0, void 0, function* () {
return yield readFile(path, 'utf-8');
});
}
tsc --target ES2015
// index.ts
async function getFileContents (path: string): Promise<string> {
return await readFile(path, 'utf-8')
}
// index.js
async function getFileContents(path) {
return await readFile(path, 'utf-8');
}
tsc --target ES2017
// index.ts
enum HTTPErrors {
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
// index.js
var HTTPErrors;
(function (HTTPErrors) {
HTTPErrors[HTTPErrors["BadRequest"] = 400] = "BadRequest";
HTTPErrors[HTTPErrors["Unauthorized"] = 401] = "Unauthorized";
HTTPErrors[HTTPErrors["Forbidden"] = 403] = "Forbidden";
HTTPErrors[HTTPErrors["NotFound"] = 404] = "NotFound";
})(HTTPErrors || (HTTPErrors = {}));
tsc
// index.ts
enum HTTPErrors {
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
ECMAScript Enum
// index.js
// index.ts
const enum HTTPErrors {
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
tsc
// index.ts
const enum HTTPErrors {
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
console.log(HTTPErrors.BadRequest);
// index.js
console.log(400 /* HTTPErrors.BadRequest */);
tsc
// index.js
var HTTPErrors;
(function (HTTPErrors) {
HTTPErrors[HTTPErrors["BadRequest"] = 400] = "BadRequest";
HTTPErrors[HTTPErrors["Unauthorized"] = 401] = "Unauthorized";
HTTPErrors[HTTPErrors["Forbidden"] = 403] = "Forbidden";
HTTPErrors[HTTPErrors["NotFound"] = 404] = "NotFound";
})(HTTPErrors || (HTTPErrors = {}));
console.log(HTTPErrors.BadRequest);
babel --presets @babel/preset-typescriptΒ
// index.ts
const enum HTTPErrors {
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
console.log(HTTPErrors.BadRequest);
// http-errors.ts
export const enum HTTPErrors {
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
// index.ts
import { HTTPErrors } from './http-errors';
console.log(HTTPErrors.BadRequest);
// http-errors.js
// index.js
import { HTTPErrors } from './http-errors';
console.log(HTTPErrors.BadRequest);
babel
babel
// index.js
console.log(400 /* HTTPErrors.BadRequest */);
tsc
// http-errors.ts
export const enum HTTPErrors {
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
// index.ts
import { HTTPErrors } from './http-errors';
console.log(HTTPErrors.BadRequest);
// http-errors.js
// index.ts
import { resolve } from 'path';
const CWD = process.cwd();
const SRC = './src';
export const RESOVLED = resolve(CWD, SRC);
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RESOVLED = void 0;
const path_1 = require("path");
const CWD = process.cwd();
const SRC = './src';
exports.RESOVLED = (0, path_1.resolve)(CWD, SRC);
tsc --module CommonJS
define(["require", "exports", "path"], function (require, exports, path_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RESOVLED = void 0;
const CWD = process.cwd();
const SRC = './src';
exports.RESOVLED = (0, path_1.resolve)(CWD, SRC);
});
tsc --module AMD
// index.ts
import { resolve } from 'path';
const CWD = process.cwd();
const SRC = './src';
export const RESOVLED = resolve(CWD, SRC);
import { resolve } from 'path';
const CWD = process.cwd();
const SRC = './src';
export const RESOVLED = resolve(CWD, SRC);
tsc --module ESNext
// index.ts
import { resolve } from 'path';
const CWD = process.cwd();
const SRC = './src';
export const RESOVLED = resolve(CWD, SRC);
type EmailAddress = `${string}@${string}`;
const birthday: Date;
function power (base: number, exponent: number): number {
return base ** exponent;
}
type Location = {
lat: number;
lng: number;
address: string;
}
interface Booking {
date: Date;
location: Location;
}
class MeetingBooking implements Booking {
public constructor (
public date: Date,
public location: Location,
public participants: Array<EmailAddress>
) { }
}
Declaring variables
Declaring functions
Declaring types
Declaring interfaces
Declaring more types
Declaring classes
declare namespace global {
interface Window {
mySweetGlobal: string;
}
}
declare module "my-js-lib" {
export function sweetAsyncRandom (): Promise<number>;
}
declare module "*.scss" {
const styles: Record<string, string>;
export default styles;
}
Augmenting the global
namespace with new global variables!
Manually declaring rough types for a third-party module
Manually declaring types for non-JS modules
true
123_456_789
'hello world'
[]
{}
null
undefined
boolean
number
string
Array<T>
Object
null
undefined
true
86_400
'January'
[number, number]
{ id: string }
null
undefined
let name = 'Craig';
typeof name; // string is inferred
const address: string = null; // β
in non-strict mode
const city: string = null; // β in strict mode
const country: string | null = null; // β
in stict mode
const age = 33;
typeof age; // 33 is inferred
const isHungry: true = false; // β
const isSleepy: boolean = false; // β
const location: string = 10000; // β
TypeScript uses the typeof
keyword to access the type of a variable!
undefined
true
86_400
'January'
[number, number]
{ id: string }
null
.valueOf()
.toPrecision(2)
.charAt(0)
[0]
['id']
true
1
'A'
false
1.1
1.1234
-1
NaN
Math.PI
666
"B"
`Cc`
'WOW!'
'π₯'
null
boolean
null
number
string
type Craig = 'Craig';
type ThirtyThree = 33;
type Location = string;
type IsHungry = true;
type IsSleepy = boolean;
Object
object
boolean
number
string
null
undefined
symbol
const address: Object = null; // β
const country: Object = undefined; // β
Object
const name: Object = 'Craig'; // β
const age: Object = 33; // β
const location: Object = 10000; // β
const isHungry: Object = false; // β
const isSleepy: Object = false; // β
{}
object
Object
Object extends object // true
Object extends {} // true
object extends Object // true
object extends {} // true
{} extends Object // true
{} extends object // true
type Compare = A extends B ? true : false;
object
boolean
number
string
null
undefined
symbol
Array<T>
Record<T, T>
{}
Date
RegExp
class Foo
Function
[symbol, symbol]
Array<boolean>
bigint[]
[Date]
[number, ...null[]]
Array<T>
[]
bigint[]
Array<bigint>
vs
β
β
type Phone = { make: string, model: string };
type Phones = Array<Phone>;
π¨
π¨
type Scores = [number, number, number, number];
const SCORES = [100, 200, 300, 400];
typeof SCORES; // Array<number>
const SCORES = [100, 200, 300, 400] as const;
typeof SCORES; // readonly [100, 200, 300, 400]
type Scores = [100, 200, 300, 400];
const assertion
Record<string, any>
{ [key: string]: any }
{ id: string }
Record<string, Date>
Index Signature
Record<string, any>
{ [key: string]: any }
{ id: string }
let dictA: {
[key: string]: any
} = {
id: '123'
}
let dictB: Record<string, any> = {
id: '456'
}
let dictC: { id: string } = {
id: '789'
}
dictB = dictA; // β
dictA = dictC; // β
dictC = dictA; // β
const DICT = { id: '789' } as const;
typeof DICT // { readonly id: '789' }
const assertion
const DICT = { id: '789' };
typeof DICT // { id: string }
type ReadonlyID = {
readonly id: string
}
const readonlyId: ReadonlyID = { id: '123' };
readonlyId.id = '456'; // β
type ReadonlyNumbers = ReadonlyArray<number>;
type ReadonlyTuple = readonly [1, 2, 3];
type ReadonlyStrings = readonly string[];
const readonlyNumbers: ReadonlyNumbers = [1, 2, 3];
readonlyNumbers.push(4); // β
readonly
modifier
type OptionalID = {
id?: string
}
const optionalId: OptionalID = { } // β
type IDOrUndefined = {
id: string | undefined
}
const noId: IDOrUndefined = { } // β
const undefinedId: IDOrUndefined = { id: undefined } // β
Optional modifier
type NumberArrayish = {
[index: number]: number
}
const numbers: NumberArrayish = {};
numbers[0] = 0; // β
numbers['one'] = 1; // β
type Index = NumbersArrayish[0]; // number
type Strings = ReadonlyArray<string>;
type Values = Strings[number]; // string
type Value = Strings[0]; // string
type Length = Strings['length']; // number
const SCORES = [100, 200, 300, 400] as const;
type Scores = typeof SCORES[number]; // 100 | 200 | 300 | 400
type Score = typeof SCORES[0]; // 100
type TupleLength = typeof SCORES['length']; // 4
Another union type!
interface Dictionary {
[key: string]: string;
}
const dict: Dictionary = {};
type Key = Dictionary['name']; // string
interface Dictionary {
[key: string]: string;
size: number; // β
}
interface Dictionary {
[key: string]: string | number;
size: number; // β
}
Yet another union type!
type Address = {
streetName: string;
streetNumber: number;
suburb: string;
country: string;
postCode: number;
}
// 'streetName' | 'streetNumber' | 'suburb' | 'country' | 'postcode'
type Keys = keyof Address;
const keys = Object.keys(address);
typeof keys; // any
type Configuration = {
input: string,
output: string
}
function configure (config: Configuration): void {
// ... handle config
}
configure({
input: './src',
output: './dist',
}); // β
configure({
input: './src',
}); // β
() => void
Function
(a: number) => number
type FuncConstructor = {
Β new (): Func;
};
class Foo
interface Func {
Β (): void;
}
function add (a: number, b: number) {
return a + b;
};
function add (a: number, b: number): number {
return a + b;
};
vs
β
β
function add (a: number, b: number): number {
// Type 'string' is not assignable to type 'number' β
return a + b + '';
};
Function
interface FunctionConstructor {
new(...args: string[]): Function;
(...args: string[]): Function;
readonly prototype: Function;
}
declare var Function: FunctionConstructor;
interface Function {
apply(this: Function, thisArg: any, argArray?: any): any;
call(this: Function, thisArg: any, ...argArray: any[]): any;
bind(this: Function, thisArg: any, ...argArray: any[]): any;
toString(): string;
prototype: any;
readonly length: number;
arguments: any;
caller: Function;
}
this
parameter declaration
type Func = (...args: Array<unknown>) => unknown;
type FuncWithThis = (this: unknown, ...args: Array<unknown>) => unknown;
function size (this: Array<unknown>): number {
return this.length;
}
size([]); // β
size.call(1); // β
size.call([1]); // β
type EventHandler = (event: Event) => void;
function onEvent (
name: string,
handler: EventHandler
): void {
// ... register handler
}
onEvent('click', () => {
// ... handle click
}); // β
onEvent('click', (name: string) => {
// ... handle click
}); // β
type
value
β
value
β
value
β
const version: number = 1.1; // β
const NEGATIVE_BEAST: number = -666; // β
const two: 2 = 3; // β
const versionStr: string = 1.1; // β
const versionObj: object = 1.1; // β
const versionObject: Object = 1.1; // β
type MapNumber = (a: number) => number;
const noop: MapNumber = () => {}; // β
const identity: MapNumber = (n: string) => n; // β
const pow: MapNumber = (n: number) => n ** n; // β
const versionStr: string = 1.1; // β
const versionObj: object = 1.1; // β
const versionObject: Object = 1.1; // β
type
value
β
value
β
value
β
const VERSION = 1.1; // '1.1'
let version = 1.1; // number
const VERSION = 1.1; // 1.1'
let version = VERSION; // number
const name: any = 'Craig'; // β
const age: any = 33; // β
const location: any = 10000; // β
const isHungry: any = false; // β
const isSleepy: any = false; // β
const address: any = null; // β
null
undefined
any
Object
object
boolean
number
string
symbol
const name: unknown = 'Craig'; // β
const age: unknown = 33; // β
const location: unknown = 10000; // β
const isHungry: unknown = false; // β
const isSleepy: unknown = false; // β
const address: unknown = null; // β
null
undefined
unknown
Object
object
boolean
number
string
symbol
unknown
any
let value: any = 1000;
value.toString(); // Works β
value.makeItAMillion(); // Works β
let value: unknown = 1000;
value.toString(); // Error β
value.makeItAMillion(); // Error β
unknown
any
Tells TypeScript that we don't care what type an object is, so tsc
should stop type-checking this variable from here forward.
Tells TypeScript that we don't know what type an object is, so tsc
Β should make sure we figured out what type it is before we use it.
const two: number = 2;
function square(n: number): number {
return n ** 2;
}
// unnecessary but valid - `2` extends `number` β
square(two as 2);
// invalid - `number` doesn't extend `object` β
square(two as object);
// invalid - `number` extends `Object` but `Object`
// can't be passed to a `number` parameter β
square(two as Object);
const two: unknown = null;
function square(n: number): number {
return n ** 2;
}
square(two as 2);
unknown
is a top-type, so you can use a Type Assertion to cast it to anything
function toArray<T>(value?: ReadonlyArray<T> | Array<T> | T): Array<T> {
if (Array.isArray(value)) {
return value;
}
if (isUndefined(value)) {
return [];
}
return [value as T];
}
try {
// something bad happens:
} catch (error) {
console.log((error as Error)).message;
throw error;
}
any
unknown
any
unknown
never
const name: never = 'Craig'; // β
const age: never = 33; // β
const location: never = 10000; // β
const isHungry: never = false; // β
const isSleepy: never = false; // β
const address: never = null; // β
function throwError (message: string): never {
throw new Error(message);
}
type WithId = { id: string };
type BanId = { id?: never };
function createId<T extends object> (obj: T & BanId): T & WithId {
const needsId: unknown = obj;
(needsId as WithId).id = '';
return needsId as (T & WithId);
}
createId({ }); // Works `id` doesn't exist β
createId({ id: '1234 '}); // Error 'string' not assignable to 'never' β
any
unknown
never
any
unknown
never
any
never
null
string
boolean
null
null
'January'
3.5
'January'
3.5
true
false
boolean
true
false
boolean
type Bool = true | false;
type Bool = boolean;
type Num = 1 | 2 | 3 | 4 | ...
type Num = number;
1
2
3
4
number
5
6
7
8
9
10
...
number
1
1
3
4
2
5
6
7
8
9
10
11
12
13
14
...
type HTTPError = 400 | 401 | 403 | 404;
const enum HTTPError {
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404
}
const HTTP_ERRORS = [400, 401, 403, 404] as const;
type HTTPErrors = (typeof HTTP_ERRORS)[number];
const HTTP_ERRORS = [400, 401, 403, 404] as const;
type HTTPErrors = (typeof HTTP_ERRORS)[number];
function isHTTPError (input: unknown): HTTPErrors {
// ???
}
const HTTP_ERRORS = [400, 401, 403, 404] as const;
type HTTPErrors = (typeof HTTP_ERRORS)[number];
function isHTTPError (input: unknown): HTTPErrors {
if (typeof input === 'number') {
return input;
}
// ???
}
const HTTP_ERRORS = [400, 401, 403, 404] as const;
type HTTPErrors = (typeof HTTP_ERRORS)[number];
function isHTTPError (input: unknown): HTTPErrors {
if (
input === 400 || input === 401 ||
input === 403 || input === 404
) {
return input;
}
// ???
}
const HTTP_ERRORS = [400, 401, 403, 404] as const;
type HTTPErrors = (typeof HTTP_ERRORS)[number];
function isHTTPError (input: unknown): HTTPErrors {
if (HTTP_ERRORS.includes(input))) {
return input;
}
// ???
}
const HTTP_ERRORS = [400, 401, 403, 404] as const;
type HTTPErrors = (typeof HTTP_ERRORS)[number];
function isHTTPError (input: unknown): input is HTTPErrors {
return HTTP_ERRORS.includes(input as HTTPErrors);
}
type predicate!
const HTTP_ERRORS = [400, 401, 403, 404] as const;
type HTTPErrors = (typeof HTTP_ERRORS)[number];
function isHTTPError (input: unknown): input is HTTPErrors {
return HTTP_ERRORS.includes(input as HTTPErrors);
}
function logHTTPError (input: unknown): void {
if (isHTTPError(input)) {
// typeof input
console.log('Oh no!');
} else {
// typeof input
console.log('Cool.')
}
}
type predicate!
const HTTP_ERRORS = [400, 401, 403, 404] as const;
type HTTPErrors = (typeof HTTP_ERRORS)[number];
function assertHTTPError (input: unknown): asserts input is HTTPErrors {
if (!HTTP_ERRORS.includes(input as HTTPErrors)) {
throw new Error();
}
}
function logHTTPError (input: unknown): void {
assertHTTPError(input);
// typeof input
expensiveLoggingOperation('Oh no!');
}
type WithName = { name: string };
type WithId = { id: string };
type Person = {
dateOfBirth: Date
} & WithName & WithId;
interface Person extends WithName, WithId {
dateOfBirth: Date
};
class Person implements WithName, WithId {
public name: string;
public id: string;
public dateOfBirth: Date;
};
interface Person extends WithName, WithId {
dateOfBirth: Date
};
interface Person {
placeOfBirth: string;
}
// lib.es2015.collection.d.ts
interface Set<T> {
add(value: T): this;
clear(): void;
delete(value: T): boolean;
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void;
has(value: T): boolean;
readonly size: number;
}
// lib.es2015.iterable.d.ts
interface Set<T> {
[Symbol.iterator](): IterableIterator<T>;
entries(): IterableIterator<[T, T]>;
keys(): IterableIterator<T>;
values(): IterableIterator<T>;
}
type Email = string;
'A'
"B"
`Cc`
'WOW!'
'π₯'
string
type Email = `${string}@${string}`;
'a@a'
"foo@bar.com"
`π₯@β€οΈ`
Email
Array<T>
Record<string, any>
Set<T>
Promise<T>
<T>(arg: T) => T
function identity (input) {
return input;
}
function identity (input: any): any {
return input;
}
const i = identity('hello');
typeof i; // any
function identity (input: unknown): unknown {
return input;
}
const i = identity('hello');
typeof i; // unknown
function identity <T> (input: T): T {
return input;
}
const i = identity('hello');
typeof i; // 'hello'
function identity <Input> (input: Input): Input {
return input;
}
const i = identity<string>('hello'); // i: string
[symbol, symbol]
Array<boolean>
bigint[]
[Date]
[number, ...null[]]
Array<T>
[]
Array<number>
[number]
[1, 2, 3]
import { promises as fs } from 'fs'
async function persist <T> (input: T): Promise<T> {
await fs.writeFile('', JSON.stringify(input));
return input;
}
const i = persist('hello'); // i: Promise<string>
import { promises as fs } from 'fs'
async function persist <T> (input: T): Promise<T> {
// Property 'serialise' does not exist on type 'T'
await fs.writeFile('', input.serialise());
return input;
}
Behaves similarly to unknown
import { promises as fs } from 'fs'
type Serialisable = {
serialise(): string;
}
async function persist <T extends Serialisable> (input: T): Promise<T> {
await fs.writeFile('', input.serialise());
return input;
}
// Argument of type 'string' is not assignable to parameter of type 'Serialisable'
const error = persist('hello');
const good = persist({ serialise: () => 'hello' });
interface Set<T> {
add(value: T): this;
clear(): void;
delete(value: T): boolean;
forEach(
callbackfn: (value: T, value2: T, set: Set<T>) => void,
thisArg?: any
): void;
has(value: T): boolean;
readonly size: number;
}
forEach
on a Set
is a little bit weird, it gets each value passed twice!
typefunction Set (Item) {
return {
add(value: Item): this;
clear(): void;
delete(value: Item): boolean;
forEach(/* ... */): void;
has(value: Item): boolean;
readonly size: number;
};
}
type SetNumber = Set<number>;
// {
// add(value: number): this;
// clear(): void;
// delete(value: number): boolean;
// forEach(/* ... */): void;
// has(value: number): boolean;
// readonly size: number;
// }
class Runner <RunResult> {
public constructor (private task: () => RunResult) { }
}
const inferredRunner = new Runner(() => 3);
type InferredRunner = typeof inferredRunner; // Runner<number>
class Runner <RunResult extends () => unknown> {
public constructor (private task: RunResult) { }
public run () {
return this.task();
}
}
const badRunner = new Runner(3); // β
const goodRunner = new Runner(() => 3); // β
type GoodRunner = typeof runner; // Runner<() => 3>
function getID <T> (input: T): T['id'] {
return input.id;
}
function getID <T extends { id: unknown }> (input: T): T['id'] {
return input.id;
}
const id = getID({ id: '123' });
This won't work
type USD = `US$${number}`
type Currency<
Prefix extends string,
Suffix extends string = '',
Separator extends string = ',',
Decimal extends string = '.'
> = ...
type USD = Currency<'US$'>;
type Euro = Currency<'β¬' | '', 'β¬' | '', '.', ','>;
typefunction Currency (
Prefix: string,
Suffix: string = '',
Separator: string = ',',
Decimal: string = '.'
) {
return `${Prefix}...`;
}
type Letter = 'a' | 'b' | 'c' | 'd' | 'e' // ...
type Numbers = 0 | 1 | 2 | 3 | 4 | 5 // ...;
// "a0" | "a4" | "a2" | "a3" | "a1" | "a5" | "a6" | "a7" | "a8" | "a9" |
// "b0" | "b4" | "b2" | "b3" | "b1" | "b5" | ...
type IDs = `${Letter}${Numbers}`;
typefunction Id (
Letters: Array<string>,
Numbers: Array<number>
): Array<string> {
const result = [];
for (let letter in Letters) {
for (let num in Numbers) {
result.push(`${letter}${num}`);
}
}
return result;
}
type HTMLTagsLower = 'a' | 'div' | 'kbd' | 'section' | ...
type HTMLTags = HTMLTagsLower | Uppercase<HTMLTagsLower>;
type BlockCaps = Lowercase<string>;
const badPassword: BlockCaps = 'HELLO'; // β
const goodPassword: BlockCaps = 'hello'; // β
type EventNames = 'click' | 'doubleclick' | 'hover';
// 'onClick' | 'onDoubleclick' | 'onHover';
type EventHandlers = `on${Capitalize<EventNames>}`;
type DotNetType = {
Name: string;
DateOfBirth: Date;
}
// 'name' | 'dateOfBirth';
type JSTypeNames = Uncapitalize<keyof DotNetType>;
Back here ready to go by 1pm!
Input extends Super ? TrueType : FalseType
typefunction Conditional (
Input,
Super,
TrueType,
FalseType,
) {
if (Input extends Super) {
return TrueType;
}
return FalseType;
}
type AssertExtends<
SuperType,
Subtype extends SuperType
> = Subtype extends SuperType ? Subtype : never;
typefunction AssertExtends (
SuperType,
SubType
) {
if (SubType extends SuperType) {
return SubType;
}
return never;
}
type IsString1<Input> = Input extends string
? Input
: never;
type IsString2<Input extends string> = Input extends string
? Input
: never;
type Str1 = IsString1<false>;
type Str2 = IsString2<false>;
No error - Str1
is never
Error: Type boolean
Β does not satisfy the constraint string
type Func = (...args: Array<unknown>) => unknown;
type IsFunction<Input> = Input extends Func
? Input
: never;
type ReturnType<Input> =
Input extends (...args: Array<unknown>) => infer Return
? Return
: never;
infer is β¨magic β¨
type FunctionMagic<Input extends Func> =
Input extends (...args: infer Parameters) => infer Return
? [Parameters, Return]
: never;
typefunction FunctionMagic (
Input: Func
) {
if (Input extends Func) {
const Parameters = gimmeParams(Input);
const Return = gimmeReturn(Input);
return [Parameters, Return];
}
return never;
}
type Domain<Input extends string> =
Input extends `${string}@${infer D}`
? D
: never;
type Gmail = Domain<'craig@gmail.com'>; // gmail.com
type Nope = Domain<'12345'>; // never
type Domain<Input extends string> =
Input extends `${string}@${infer D}`
? D
: { message: `"${Input}" is not an email address`};
type Gmail = Domain<'craig@gmail.com'>; // gmail.com
// { message: "\"12345\" is not an email address" }
type Nope = Domain<'12345'>;
type Bundles {
'index.js': string,
'1.js': string,
'2.js': string
};
type Keys = keyof Bundles; // 'index.js' | '1.js' | '2.js'
type FuncyBundles = {
[Path in keyof Bundles]: () => string
};
// type FuncyBundles = {
// 'index.js': () => string;
// '1.js': () => string;
// '2.js': () => string;
// }
typefunction FuncyBundles () {
const Result = {};
const Paths = keyof Bundles;
Paths.forEach(Path => {
Result[Path] = () => string;
});
return Result;
};
Should probably be a map
or a reduce
, but this seems clearer
type LazyBundles = {
[Path in keyof Bundles]: () => Promise<Bundles[Path]>
};
// type LazyBundles = {
// 'index.js': () => Promise<string>;
// '1.js': () => Promise<string>;
// '2.js': () => Promise<string>;
// }
typefunction LazyBundles () {
const Result = {};
const Paths = keyof Bundles;
Paths.forEach(Path => {
Result[Path] = () => Promise<Bundles[Path]>;
});
return Result;
};
type LazyBundlesExceptIndex = {
[Path in keyof Bundles]: Path extends 'index.js'
? Bundles[Path]
: () => Promise<Bundles[Path]>
};
// type LazyBundlesExceptIndex = {
// 'index.js': string;
// '1.js': () => Promise<string>;
// '2.js': () => Promise<string>;
// }
typefunction LazyBundlesExceptIndex () {
const Result = {};
const Paths = keyof Bundles;
Paths.forEach(Path => {
if (Path === 'index.js') {
Result[Path] = Bundles[Path];
} else {
Result[Path] = () => Promise<Bundles[Path]>;
}
});
return Result;
};
type LazyBundles = {
[Path in keyof Bundles as `get${Capitalize<Path>}`]:
() => Promise<Bundles[Path]>
};
// type LazyBundles = {
// "getIndex.js": () => Promise<string>;
// "get1.js": () => Promise<string>;
// "get2.js": () => Promise<string>;
// }
typefunction LazyBundles () {
const Result = {};
const Paths = keyof Bundles;
Paths.forEach(Path => {
Result[`get${Capitalize(Path)}`] = () => Promise<Bundles[Path]>;
});
return Result;
};
type LazyBundles = {
[
Path in keyof Bundles as Path extends 'index.js'
? Path : never
]: () => Promise<Bundles[Path]>
};
// type LazyBundles = {
// "getIndex.js": () => Promise<string>;
// }
typefunction LazyBundles () {
const Result = {};
const Paths = keyof Bundles;
Paths.forEach(Path => {
if (Path === 'index.js') {
Result[Path] = () => Promise<Bundles[Path]>;
}
});
return Result;
};
type ConfigInput = {
src?: string;
silent?: boolean;
output?: string;
};
type Required<Input> = {
[Property in keyof Input]-?: Input[Property];
}
type ConfigOutput = Required<ConfigInput>;
// type ConfigInput = {
// src: string;
// silent: boolean;
// output: string;
// };
Kind of weird syntax!
type ReadonlyConfig = {
readonly src: string;
readonly silent: boolean;
readonly output: string;
};
type Mutable<Input> = {
-readonly [Property in keyof Input]: Input[Property];
}
type MutableConfig = Mutable<ReadonlyConfig>;
// type ConfigInput = {
// src: string;
// silent: boolean;
// output: string;
// };
type State = {
counter: number;
items: Array<string>;
}
function updateState (state: Partial<State>): State {
return {
...oldState,
state
};
}
type Partial<T> = unknown;
type ConfigInput = {
src?: string;
silent?: boolean;
output?: string;
};
type ConfigOutput = Required<ConfigInput>;
type Required<T> = unknown;
type ConfigInput = {
src?: string;
silent?: boolean;
output?: string;
};
type ConfigOutput = Required<ConfigInput>;
type ImmutableCounfig = Readonly<ConfigOutput>;
type Readonly<T> = unknown;
type Things = [1, 2, 'hello', null, RegExp];
// 2 | 1
type Numbers = Extract<Things[number], number>;
// RegExp | null | 'hello'
type NonNumbers = Exclude<Things[number], number>;
type Exclude<T, U> = unknown;
type Extract<T, U> = unknown;
type Address = {
streetName: string;
streetNumber: number;
suburb: string;
country: string;
postCode: number;
}
type Addressish = Pick<Address, 'suburb' | 'country'>;
type Addressish2 = Omit<Address, 'streetName' | 'streetNumber' | 'postCode'>;
type Pick<T, K extends keyof T> = unknown;
type Omit<T, K extends keyof any> = unknown;
// string
type JustString = NonNullable<string | null | undefined>;
type NonNullable<T> = unknown;
type ReturnType<Input> = unknown;
type Parameters<Input> = unknown;
function add (a: number, b: number): number {
returb a + b;
}
type Params = Parameters<typeof add>; // [a: number, b: number]
type Result = ReturnType<typeof add>; // number
type ConstructorParameters<
T extends abstract new (...args: any) => any
> = unknown;
class Person {
public constructor (name: string, age: string) { }
}
// Type 'typeof Person' does not satisfy the constraint '(...args: any) => any'
type BadParams = Parameters<typeof Person>; // β
// [name: string, age: string]
type GoodParams = ConstructorParameters<typeof Person>;
type InstanceType<
T extends abstract new (...args: any) => any
> = unknown;
class Person {
public constructor (name: string, age: string) { }
}
// Type 'typeof Person' does not satisfy the constraint '(...args: any) => any'
type BadReturnType = ReturnType<typeof Person>; // β
// Person
type GoodInstanceType = InstanceType<typeof Person>;
type ThisParameterType<T> = unknown;
function size (this: Array<unknown>): number {
return this.length;
}
type SizeThis = ThisParameterType<typeof size>; // Array<unknown>
interface CallableFunction extends Function {
// ...
bind<T>(this: T, thisArg: ThisParameterType<T>): OmitThisParameter<T>;
}
class Toggle {
constructor(props) {
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({ ... }));
}
render() {
return (
<button onClick={this.handleClick}></button>
);
}
}
type OmitThisParameter<T> = unknown;
type Awaited<T> = unknown;
type AsyncNumber = Promise<number>;
type SyncNumber = Awaited<AsyncNumber>; // number
type ReallySyncNumber = Awaited<Promise<Promise<number>>; // number;