- Generic type variables
- Generic functions
- Generic interfaces
- Generic classes
- Generic constraints
- Template literal types
- Indexed access types
- Typeof type operator
interface Visitor {
id: string;
}
interface Subscriber extends Visitor {
annualMembership: boolean;
trial: boolean;
}
interface Guest extends Visitor {
firstVisit: boolean;
}
let subscriber: Subscriber = {
id: "1",
annualMembership: true,
trial: false,
};
let guest: Guest = {
id: "2",
firstVisit: false,
};
const isSubscriber = (value: Visitor): value is Subscriber => {
return (value as Subscriber).annualMembership !== undefined;
};
const isGuest = (value: Visitor): value is Guest => {
return (value as Guest).id !== undefined;
};
const getUserType = () => {
if (isSubscriber(subscriber)) {
return (
"Annual membership " +
subscriber.annualMembership +
"Trial expired: " +
subscriber.trial
);
}
if (isGuest(guest)) {
"Guest Id: " + console.log(guest.id);
}
};
interface User<T> {
title: T;
}
let user: User<string>;
type Account<T> = {
id: T;
};
interface Personal {}
type PersonalAccount = Account<Personal>;
// *** Generic helper types
type OrNull<Type> = Type | null;
type OneOrMany<Type> = Type | Type[];
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
// type OneOrManyOrNull<Type> = OneOrMany<Type> | null;
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
// type OneOrManyOrNullStrings = OneOrMany<string> | null;
interface Planet {
meanDiameter: number;
earthVolume: number;
}
const updatePlanet = (
planet: Planet,
physicalCharacteristics: Partial<Planet>
) => {
return { ...planet, ...physicalCharacteristics };
};
const mercury = {
meanDiameter: 4880,
earthVolume: 0.055,
};
const updatedMercury = updatePlanet(mercury, { meanDiameter: 4890 });
interface Mammal {
kingdom?: string;
phylum?: string;
}
const mammal: Required<Mammal> = { kingdom: "Animalia" };
// Property 'phylum' is missing in type '{ kingdom: string; }' but required in type 'Required<Mammal>'.ts(2741)
interface Position {
x: number;
y: number;
}
const immutableLocation: Readonly<Position> = {
x: 0,
y: 0,
};
immutableLocation.x = 1;
// Cannot assign to 'x' because it is a read-only property.ts(2540)
interface Boxer {
active: boolean;
stance?: "orthodox" | "southpaw";
}
type Name = "Mike Tyson" | "Micky Ward" | "Vasiliy Lomachenko";
const boxers: Record<Name, Boxer> = {
"Mike Tyson": { active: false },
"Vasiliy Lomachenko": { active: true, stance: "southpaw" },
"Micky Ward": {
active: false,
},
};
interface Post {
id: string;
createdBy: string;
lastUpdated: string | null;
title: string;
}
type PostSummary = Pick<Post, "id" | "title">;
const post: PostSummary = {
id: "1",
title: "Lorem ipsum",
};
type PostId = Omit<Post, "createdBy" | "lastUpdated" | "title">;
// *** Exclude<Type, ExcludedUnion>
type Middleweight = Exclude<
"Mike Tyson" | "Gennady Golovkin" | "Jermall Charlo",
"Mike Tyson"
>;
// type MiddleWeights = "Gennady Golovkin" | "Jermall Charlo"
type Heavyweight = Exclude<"Mike Tyson", "Gennady Golovkin" | "Jermall Charlo">;
// type Heavyweight = "Mike Tyson"
type FunctionOnly = Exclude<
string | number | (() => Promise<void>),
string | number
>;
type Falafel = Extract<"hummus" | "bread", "bread" | "cheese">;
// type Falafel = "bread"
type Kebab = NonNullable<"yummy" | null | undefined>;
// type Kebab = "yummy"
// *** Parameters<Type>
type T1 = Parameters<(s: Array<string>, c: Array<number>) => Promise<void>>;
// type T1 = [s: string[], c: number[]]
const helloDave = (x: number, y: number) => {
return x + y;
};
type helloDaveT = ReturnType<typeof helloDave>;
// type helloDaveT = number
type imSorryDave = ReturnType<() => Promise<void>>;
// type imSorryDave = Promise<void>
interface User {
length: number;
}
type U = ReturnType<<T extends User, User extends Array<string>>() => T>;
// type U = string[]
enum HeavyweightBoxers {
"Mike Tyson" = 1,
"Evander Holyfield", // 2
"George Foreman", // 3
"Lennox Lewis", // 4
}
enum MexicanBoxers {
"Jaime Munguia", // 0
"Oscar de la Hoya", // 1
"Julio Cesar Chavez", // 2
}
enum Membership {
Annual, // 0
Monthly, // 1
Trial, // 2
}
enum State {
pending = "PENDING",
cancelled = "CANCELLED",
fulfilled = "FULFILLED",
failed = "FAILED",
}
console.log(State.cancelled); // "CANCELLED"
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length,
}
const sendMessage = <T>(message: T): T => {
return message;
};
const newMessage = sendMessage("Hello, world!");
const getMessages = <T>(messages: Array<T>): Array<T> => {
console.log(messages.length);
return messages;
};
// Another way
interface ArrLength {
length: number;
}
const getMessages = <T extends ArrLength>(messages: T): T => {
console.log(messages.length);
return messages;
};
const foo = <T>(arg: T): T => {
return arg;
};
let bar: <T>(arg: T) => T = foo;
interface GenericInterface<T> {
(arg: T): T;
}
const whoAmI = <T>(name: T): T => {
return name;
};
let identity: GenericInterface<number> = whoAmI;
class GenericClass<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let genericInstance = new GenericClass<number>();
genericInstance.zeroValue = 0;
genericInstance.add = function (x, y) {
return x + y;
};
const getProperty = <T, K extends keyof T>(obj: T, key: K) => {
return obj[key];
};
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
getProperty(x, "m"); // Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
type Film = { title: string; releaseYear: number };
type FilmKeys = keyof Film; // "title" | "releaseYear"
type FirstName = "Mike";
type LastName = "Tyson";
type Greeting = `Hello, ${FirstName} ${LastName}`;
// type Greeting = "Hello, Mike Tyson"
type User = "username" | "account";
type MembershipType = "paid" | "trial";
type IDs = `${User | MembershipType}_id`;
// type IDs = "username_id" | "account_id" | "paid_id" | "trial_id"
type PropEventSource<Type> = {
on(
eventName: `${string & keyof Type}Changed`,
callback: (newValue: any) => void
): void;
};
/// Create a "watched object" with an 'on' method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<Type>(
obj: Type
): Type & PropEventSource<Type>;
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
});
person.on("firstNameChanged", () => {});
person.on("firstName", () => {});
// Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
person.on("frstNameChanged", () => {});
// Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
type AnotherPropEventSource<Type> = {
on<Key extends string & keyof Type>(
eventName: `${Key}Changed`,
callback: (newValue: Type[Key]) => void
): void;
};
declare function makeWatchedObject<Type>(
obj: Type
): Type & AnotherPropEventSource<Type>;
const anotherPerson = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
});
person.on("firstNameChanged", (newName) => {
// (parameter) newName: string
console.log(`new name is ${newName.toUpperCase()}`);
});
person.on("ageChanged", (newAge) => {
// (parameter) newAge: number
if (newAge < 0) {
console.warn("warning! negative age");
}
});
type aGreeting = "Hello, world";
type ShoutyGreeting = Uppercase<aGreeting>;
// type ShoutyGreeting = "HELLO, WORLD"
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`;
type MainID = ASCIICacheKey<"my_app">;
// type MainID = "ID-MY_APP"
type Division =
| "flyweight"
| "bantamweight"
| "featherweight"
| "lightweight"
| "welterweight"
| "middleweight"
| "light-heavyweight"
| "heavyweight";
type ProBoxer = {
firstName: string;
lastName: string;
active: boolean;
gender: "male" | "female";
age: number;
weight: number;
height: number;
stance: "orthodox" | "southpaw";
wins: number;
losses: number;
division: Division;
};
type Stance = ProBoxer["stance"];
// type Stance = "orthodox" | "southpaw"
type GenderAge = Person["gender" | "age"];
// type GenderAge = number | "male" | "female"
type ProBoxerPropTypes = ProBoxer[keyof ProBoxer];
// type ProBoxerPropTypes = string | number | boolean
type StanceOrDivision = "stance" | "division";
type StanceDivisionProBoxerProps = ProBoxer[StanceOrDivision];
// type StanceDivisionProBoxerProps = "orthodox" | "southpaw" | Division
interface Artist {
name: string;
}
type ArtistSummary = {
[key: string]: boolean | Artist;
};
const ArtistData: ArtistSummary = {
isAnyGood: true,
// name: "Alton Ellis" - not assignable
data: {
name: "Alton Ellis",
},
};
type OptionsFlags<T> = {
[P in keyof T]: () => Promise<Array<any>>;
};
type FeatureFlags = {
getAllUsers: () => void;
getAllAccounts: () => void;
};
type FeatureOptions = OptionsFlags<FeatureFlags>;
/*
type FeatureOptions = {
getAllUsers: () => Promise<Array<any>>;
getAllAccounts: () => Promise<Array<any>>;
}
*/
// Remove 'readonly' attributes from a type's properties
type CreateMutable<T> = {
-readonly [P in keyof T]: T[P];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
/*
type UnlockedAccount = {
id: string;
name: string;
};
*/
// Remove 'optional' attributes from a type's properties
type Concrete<T> = {
[P in keyof T]-?: T[P];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type User = Concrete<MaybeUser>;
/*
type User = {
id: string;
name: string;
age: number;
};
*/
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
/*
type LazyPerson = {
getName: () => string;
getAge: () => number;
getLocation: () => string;
}
*/
// Remove type property
type RemoveKindField<T> = {
[P in keyof T as Exclude<P, "kind">]: T[P];
};
interface Circle {
kind: "circle";
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
/*
type KindlessCircle = {
radius: number;
};
*/
let quote = "Everybody has a plan until they get punched in the face.";
let mikeTyson: typeof quote; // let quote: string
type Predicate<T> = (x: T) => T;
type returnString = ReturnType<Predicate<string>>;
// type returnString = string
const foo = (arg: number): string => {
return String(arg);
};
type returnBar = ReturnType<typeof foo>;
// type returnBar = string
interface Animal {
organs: boolean;
}
interface Human extends Animal {
consciousness: boolean;
}
type Example1 = Human extends Animal ? "isHuman" : null;
// type Example1 = "isHuman";
type Example2 = Rock extends Animal ? boolean : number;
// type Example2 = number;
interface IdLabel {
id: number;
}
interface NameLabel {
name: string;
}
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
const createLabel = <T extends number | string>(idOrName: T): NameOrId<T> => {
throw "unimplemented";
};
let a = createLabel("typescript");
// let a: NameLabel
let b = createLabel(2.8);
// let b: IdLabel
let c = createLabel(Math.random() ? "hello" : 42);
// let c: NameLabel | IdLabel