Skip to content

Instantly share code, notes, and snippets.

@drmas
Created March 30, 2020 19:03
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drmas/179342d4b690d32140c498ca8edd01d1 to your computer and use it in GitHub Desktop.
Save drmas/179342d4b690d32140c498ca8edd01d1 to your computer and use it in GitHub Desktop.
EgyptJS Advanced Typescript Types
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