Created
March 30, 2020 19:03
-
-
Save drmas/179342d4b690d32140c498ca8edd01d1 to your computer and use it in GitHub Desktop.
EgyptJS Advanced Typescript Types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Title = "Advanced Typescript Types"; | |
const WhoAmI = { | |
name: `Mohamed Shaban`, | |
github: `github.com/drmas`, | |
job: "Software Engineer", | |
company: `Urban Sports Club - Berlin` | |
} | |
// # Intersection Types | |
/** | |
* | |
* > An intersection type combines multiple types into one. | |
* | |
* Add together existing types | |
* Have all the features | |
* "Person & Serializable & Loggable" | |
* | |
*/ | |
// # Example - Intersection Types | |
type Loggable = { log(): void; } | |
type Executable = { run(): void; } | |
function factory () : Loggable & Executable { | |
return ({ | |
log() { console.log('logging')}, | |
run() { console.log('running') } | |
}) | |
} | |
// -- | |
// -- | |
// # Union Types | |
/** | |
* > A union type describes a value that can be one of several types. | |
* | |
* We use the vertical bar (|) to separate each type | |
* Example "number | string | boolean" | |
*/ | |
// # Example - Union Types | |
function padLeft(value: string, padding: any) { | |
if (typeof padding === "number") { | |
return Array(padding + 1).join(" ") + value; | |
} | |
if (typeof padding === "string") { | |
return padding + value; | |
} | |
throw new Error(`Expected string or number, got '${padding}'.`); | |
} | |
const padded = padLeft("Hello world", 4); | |
console.log({padded}); | |
// -- | |
// -- | |
// # Type Guards and Differentiating Types | |
/** | |
* > To differentiate between two possible values is to check for the presence of a member | |
*/ | |
// # Example - Type Guards | |
interface Bird { fly():void; layEggs():void; } | |
interface Fish { swim():void; layEggs():void; } | |
function getSmallPet(): Fish | Bird { | |
return Math.random() < 0.5 ? { | |
fly() {console.log('flying')}, | |
layEggs() {console.log('layEggs')} | |
} : { | |
swim() {console.log('flying')}, | |
layEggs() {console.log('layEggs')} | |
}; | |
} | |
let pet = getSmallPet(); | |
pet.layEggs(); // okay | |
if (pet.swim) pet.swim(); // errors | |
// # Using type predicates | |
function isFish(pet: Fish | Bird): pet is Fish { | |
return (pet as Fish).swim !== undefined; | |
} | |
if (isFish(pet)) pet.swim(); | |
// -- | |
// # Using the in operator | |
if ("swim" in pet) { | |
pet.swim(); | |
} | |
// -- | |
// # typeof type guards | |
function padLeft2(value: string, padding: string | number) { | |
if (typeof padding === 'number') { | |
return Array(padding + 1).join(" ") + value; | |
} | |
if (typeof padding === 'string' ) { | |
return padding + value; | |
} | |
throw new Error(`Expected string or number, got '${padding}'.`); | |
} | |
// -- | |
// # instanceof type guards | |
interface Padder { getPaddingString(): string; } | |
class SpaceRepeatingPadder implements Padder { | |
constructor(private numSpaces: number) { } | |
getPaddingString() { | |
return Array(this.numSpaces + 1).join(" "); | |
} | |
} | |
class StringPadder implements Padder { | |
constructor(private value: string) { } | |
getPaddingString() { | |
return this.value; | |
} | |
} | |
function getRandomPadder() { | |
return Math.random() < 0.5 ? | |
new SpaceRepeatingPadder(4) : | |
new StringPadder(" "); | |
} | |
// Type is 'Padder' or 'SpaceRepeatingPadder | StringPadder' | |
let padder: Padder = getRandomPadder(); | |
if (padder instanceof SpaceRepeatingPadder) { | |
padder; // type narrowed to 'SpaceRepeatingPadder' | |
} | |
if (padder instanceof StringPadder) { | |
padder; // type narrowed to 'StringPadder' | |
} | |
// -- | |
// -- | |
// -- | |
// # Nullable types | |
/** | |
* > 'null' and 'undefined' assignable to anything | |
* | |
* strictNullChecks flag fixes this | |
*/ | |
// # Example - Nullable types | |
let s = "foo"; | |
s = null; | |
let sn: string | null = "bar"; | |
sn = null; | |
sn = undefined; | |
// * use ! and optional paramerters | |
const first = (s: string) => s.substr(0,1) | |
const res = first("Hello"); | |
res.toUpperCase(); | |
// -- | |
// -- | |
// # Optional Chaning | |
/** | |
* > Expressions stops if it runs into a null or undefined. | |
* | |
* Using ?. before property or function call | |
* let x = foo?.bar.baz(); | |
* let y = arr?.[0]; | |
* ! Typesctipt 3.7+ | |
*/ | |
const foo:any = {}; | |
// Before | |
if (foo && foo.bar && foo.bar.baz) { | |
// ... | |
} | |
// After-ish | |
if (foo?.bar?.baz) { | |
// ... | |
} | |
/** | |
* ! IMPORTANT | |
* '?.' !== '&&' | |
* && checks for falsy values | |
* ?. checks for undefined or null only | |
* Optional chains have is limited property accesses, calls, element accesses | |
*/ | |
function barPercentage(foo?: { bar: number }) { | |
return foo?.bar / 100; | |
} | |
// -- | |
// # Discriminated Unions | |
/** | |
* > combine singleton types, union types, type guards, and type aliases to build an advanced pattern called discriminated unions | |
* | |
* There are three ingredients: | |
* 1- Types that have a common, singleton type property — the discriminant. | |
* 2- A type alias that takes the union of those types — the union. | |
* 3- Type guards on the common property. | |
*/ | |
// # Example Discriminated Unions | |
interface Square { | |
kind: "square"; // <==== | |
size: number; | |
} | |
interface Rectangle { | |
kind: "rectangle"; // <==== | |
width: number; | |
height: number; | |
} | |
interface Circle { | |
kind: "circle"; // <==== | |
radius: number; | |
} | |
type Shape = Square | Rectangle | Circle; | |
function area(s: Shape) { | |
switch (s.kind) { | |
case "square": return s.size * s.size; | |
case "rectangle": return s.height * s.width; | |
case "circle": return Math.PI * s.radius ** 2; | |
} | |
} | |
// -- | |
// -- | |
// # Index types | |
/** | |
* > Check code that uses dynamic property names | |
* | |
* You can use 'keyof' to get the type of all keys in object | |
*/ | |
// # Example Index types | |
interface Car { | |
manufacturer: string; | |
model: string; | |
year: number; | |
} | |
type keys = keyof Car; | |
function pluck<T, K extends keyof T>(o: T, propertyNames: K[]): T[K][] { | |
return propertyNames.map(n => o[n]); | |
} | |
let taxi: Car = { | |
manufacturer: 'Toyota', | |
model: 'Camry', | |
year: 2014 | |
}; | |
// Manufacturer and model are both of type string, | |
// so we can pluck them both into a typed string array | |
let makeAndModel = pluck(taxi, ['manufacturer', 'model']); | |
// -- | |
// -- | |
// # Mapped types | |
/** | |
* > Take an existing type and make each of its properties optional for example | |
* | |
*/ | |
// # Example Mapped types | |
type ReadonlyType<T> = { | |
readonly [P in keyof T]: T[P]; | |
} | |
type PartialType<T> = { | |
[P in keyof T]?: T[P]; | |
} | |
type Nullable<T> = { | |
[P in keyof T]: T[P] | null; | |
} | |
interface Person { | |
name: string; | |
age: number; | |
address: string; | |
} | |
type PersonReadOnly = ReadonlyType<Person> | |
type PersonOptional = PartialType<Person> | |
type NullablePerason = Nullable<Person> | |
type PresonNameAge = Pick<Person, 'name' | 'age'>; | |
type Keys = 'option1' | 'option2'; | |
type Flags = { [K in Keys]: boolean }; | |
// -- | |
// -- | |
// # Unknown Type | |
/** | |
* > Unknown is the type-safe counterpart of any | |
* Anything is assignable to unknown, but unknown isn’t assignable to anything but itself and any | |
*/ | |
let a: string; | |
let x: any; | |
a = x; | |
x = a; | |
let y: unknown; | |
y = a; | |
a = y; | |
// -- | |
// # Derive Types From Constants | |
/** | |
* When you need literal types both as values and as types | |
*/ | |
const MOVES = { | |
ROCK: { beats: 'SCISSOR' }, | |
PAPER: { beats: 'ROCK' }, | |
SCISSOR: { beats: 'PAPER' }, | |
}; | |
type Move = keyof typeof MOVES; | |
const move: Move = 'ROCK'; | |
// -- | |
// # Derive Types From Function Definitions | |
/** | |
* When you need literal types both as values and as types | |
* Use 'Parameters<FunctionType>' to get params types | |
* Use 'Parameters<FunctionType>' to get params types | |
*/ | |
function createImage(image) { | |
// do some transformations | |
return createImageBitmap(image) | |
} | |
// -- | |
/** | |
* Resources: | |
* https://www.typescriptlang.org/docs/handbook/advanced-types.html | |
* https://blog.smartive.ch/fun-times-with-advanced-typescript-3167c6dfcd6a | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment