Skip to content

Commit

Permalink
checkpoint 2021-09-07
Browse files Browse the repository at this point in the history
  • Loading branch information
pero5ar committed Sep 8, 2021
1 parent e93fc6c commit 9197dab
Show file tree
Hide file tree
Showing 9 changed files with 502 additions and 54 deletions.
98 changes: 98 additions & 0 deletions 001.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// # Types 101


// ## JS Primitives

let _number: number = 1;
let _string: string = 'value';
let _boolean: boolean = true;
let _symbol: symbol = Symbol();
let _bigint: bigint = BigInt(9007199254740991);
let _undefined: undefined = undefined;

let _null: null = null;


// ## Objects

let _obj1: object = { a: 'value' }; // don't, any non primitive
let _obj2: {} = { a: 'value' }; // don't, basically any
let _obj3: { [key: string]: any; } = { a: 'value' };
let _obj4: Record<string, any> = { a: 'value' };
let _obj5: { a: string; } = { a: 'value' };

type UnknownObject = Record<string, unknown>;
let _obj6: UnknownObject = { a: 'value' };


// ## Functions

let _fun1: Function = (x) => x++; // don't
let _fun2: (...args: any[]) => any = (x) => x++; // you can do better
let _fun3: (x: any) => any = (x) => x++;
let _fun4: (x: number) => number = (x) => x++;

// Remember: functions are objects
let _fun5: object = (x) => x++;
let _fun6: {} = (x) => x++;

// Bur now needs cast to call:
let _callableFun5 = _fun5 as Function;
_callableFun5();


// ## Any, unknown, never

function getA1(obj: any) {
return obj.a;
}

function getA2(obj: unknown) {
// return obj.a; -> Property 'a' does not exist on type 'unknown'.ts(2339)
if (isObjWithA(obj)) {
// obj is { a: unknown } in this block
obj.a;
}
return undefined;
}

/** This is called a Type Guard */
function isObjWithA(obj: unknown): obj is { a: unknown } {
// NOTE: null check required only because of config "strictNullChecks": true
return typeof obj === 'object' && obj !== null && 'a' in obj;
}

function throwError(msg: string): never {
throw new Error(msg);
}


// ## Literals

let _true: true = true;
let _successfulResponse: 200 | 201 | 204 = 200;
let _flexDirection: 'row' | 'row-reverse' | 'column' | 'column-reverse' = 'row';

// Template Literals (TS >= 4.1)
let _flexDirection2: `${'row' | 'column'}${'' | '-reverse'}` = 'row';

type LowercaseHttpVerbs = 'get' | 'head' | 'post' | 'put' | 'patch' | 'delete';
type HttpVerbs = LowercaseHttpVerbs | Uppercase<LowercaseHttpVerbs>;
// result: LowercaseHttpVerbs | "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE"
// see: Uppercase, Lowercase, Capitalize, Uncapitalize


// ## Arrays and tuples

let _strArr1: string[] = ['val1', 'val2'];
let _strArr2: Array<string> = ['val1', 'val2'];

let _strOrNumArr1: (number | string)[] = [1, 'val2'];
let _strOrNumArr2: Array<number | string> = [1, 'val2'];

let _tuple1: [string, string] = ['val1', 'val2'];
let _tuple2: [string, number, string] = ['val1', 2, 'val3'];
let _tuple3: [string, number, string?] = ['val1', 2];
let _tuple4: [string, ...number[]] = ['val1', 2, 3, 4];
let _tuple5: [string, ...number[], string] = ['val1', 2, 3, 4, 'val5']; // TS >= 4.2
let _tuple6: [first: string, second: string] = ['val1', 'val2'];
141 changes: 141 additions & 0 deletions 002.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// # Unions and Intersections


// ## Basic example

type SingleDigitPrime = 2 | 3 | 5 | 7;
type SingleDigitOdd = 1 | 3 | 5 | 7 | 9;

type SingleDigitPrimeOrOdd = SingleDigitPrime | SingleDigitOdd;
// result: 2 | 3 | 5 | 7 | 1 | 9

type SingleDigitPrimeAndOdd = SingleDigitPrime & SingleDigitOdd;
// result: 3 | 5 | 7


// ## Object union

// Basically an or operator

type Human = { firstName: string; lastName: string; address: string; oib: string; };
interface Company { companyName: string; address: string; oib: string; };

declare function verifyOIB(oib: string): boolean;
// declare = I'm to lazy to write a function body

function payTaxes(entity: Human | Company): void {
verifyOIB(entity.oib);
// entity.companyName; -> Property 'companyName' does not exist on type 'Human'.ts(2339)

let name: string;
if ('companyName' in entity) {
// `entity: Company` in this block
name = entity.companyName;
} else {
// `entity: Human` in this block
name = entity.firstName + ' ' + entity.lastName;
}
// other stuff...
}


// # Object intersection

interface DbModel { id: number; }
type ChildObject = { parentId: number; };

type ChildDbModel = DbModel & ChildObject;

function addChild(parentId: number, child: DbModel): ChildDbModel {
return {
...child,
parentId,
}
}


// ## Narrowing down types with intersections

type Bipedal = { usesFourLegs: false; usesTwoLegs: true; };
type Quadrupedal = { usesFourLegs: true; usesTwoLegs: false; };

type Dinosaur = (Bipedal | Quadrupedal) & {
laysEggs: true;
haveFeathers?: boolean;
canFly?: boolean; warmBlooded?: boolean
};

type Theropod = Dinosaur & Bipedal;
const tRex: Theropod = {
canFly: false,
laysEggs: true,
usesFourLegs: false,
usesTwoLegs: true,
};

type Bird = Theropod & { haveFeathers: true; canFly: boolean; warmBlooded: true; };
const chicken: Bird = {
canFly: false,
haveFeathers: true,
laysEggs: true,
usesFourLegs: false,
usesTwoLegs: true,
warmBlooded: true,
};


// ## Side note: optional is not the same as undefined

let _obj7: { a?: string } = {};

// let _obj7: { a: string | undefined } = {};
// result: Property 'a' is missing in type '{}' but required in type '{ a: string | undefined; }'.ts(2741)


// ## interface extends

// Basically the same as object intersections

interface Bird2 extends Theropod {
haveFeathers: true;
canFly: boolean;
warmBlooded: true;
}
const chicken2: Bird2 = chicken;

// But with restrictions

// interface Bird3 extends Dinosaur {}
// result: An interface can only extend an object type or intersection of object types with statically known members.ts(2312)


// ## A "real" union of objects

// Goal: shared types are required and individual ones are optional

type LegalEntity = (Human | Company) & Partial<Human> & Partial<Company>;

function payTaxes2(entity: LegalEntity): void {
verifyOIB(entity.oib);

const name: string = entity.companyName ?? ((entity.firstName ?? '') + ' ' + (entity.lastName ?? ''));
// other stuff...
}


// ## Side note: JS nullish coalescing && optional chaining

function old_getChildId(parent: any): number | null {
if (parent === null || parent === undefined) return null;
if (parent.child === null || parent.child === undefined) return null;
// NOTE: Can't use `parent.child.id || null`, because id can be 0
if (parent.child.id === null || parent.child.id === undefined) return null;
return parent.child.id;
}

function new_getChildId(parent: any): number | null {
return parent?.child?.name ?? null;
}

globalThis.notSureIfThisFunctionExists?.();

144 changes: 144 additions & 0 deletions 003.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// # TS Language Extensions


// ## Type vs interface

// Basically the same thing since TS 2.7

type ObjectType = {
a: string;
}
interface ObjectInterface {
a: string;
}

// Key differences:

// 1st: Types are basically aliases
type MyString = string; // you can't map a primitive to an interface
let _myString: MyString; // result: let _myString: string

// 2nd: Interfaces are open to expansion (a.k.a. declaration merging)
interface ObjectInterface {
a: string;
}
interface ObjectInterface {
b: string;
}
let _myObject: ObjectInterface;
_myObject!.a // string
_myObject!.b // string

// Side note: Notice the `!` part, use it wisely

// This throws an error:
// type ObjectType = {
// b: string;
// }

// Rule of thumb: Use interface for defining objects, and type for complex types
// Please use interfaces when writing libraries


// ## enums

enum StandardIOStream {
STDIN,
STDOUT,
STDERR,
}

StandardIOStream[0] // "STDIN"
StandardIOStream.STDIN // 0

Object.entries(StandardIOStream)
// [["0", "STDIN"], ["1", "STDOUT"], ["2", "STDERR"], ["STDIN", 0], ["STDOUT", 1], ["STDERR", 2]]

// Common use case (avoiding duplicates)

enum Direction {
UP = "UP",
DOWN = "DOWN",
LEFT = "LEFT",
RIGHT = "RIGHT",
}

Object.entries(Direction).length // 4


// ## keyof, typeof

interface Person {
firstName: string;
middleName?: string;
lastName: string;
}
let _peronProp: keyof Person; // 'firstName' | 'middleName' | 'lastName'

let _john = { firstName: 'John', lastName: 'Smith' }

type Person2 = typeof _john; // { firstName: string; lastName: string; }


// ## Using typeof instead of copy paste

declare function oddlySpecificDeepCopyAsync(
data: { personInfo: Partial<Person>; direction: Direction | null; /* ... */ }
): Promise<typeof data>


// ## Get type from array

let _mixedArray = [1, 'something', null, 'literalString' as const]


// ## Constructing objects via keyof

interface NameUseLookup {
[name: string]: {
[key in keyof Person]?: true;
}
}
let _nameUseLookup: NameUseLookup = {
'John': { firstName: true, middleName: true },
'Smith': { lastName: true }
}


// ## Using them on enums

type StandardIOStreamName = keyof typeof StandardIOStream;
// "STDIN" | "STDOUT" | "STDERR"

type StandardIOStreamDict = {
[name in StandardIOStreamName]: (typeof StandardIOStream)[name]
};
const standardIOStreamDict: StandardIOStreamDict = {
STDIN: StandardIOStream.STDIN,
STDOUT: StandardIOStream.STDOUT,
STDERR: StandardIOStream.STDERR,
// STDERR: -1, -> be careful, this is valid
}


// ## as const

let _john2 = { firstName: 'John', lastName: 'Smith' } as const;

type JohnSmith = typeof _john2;
// { readonly firstName: "John"; readonly lastName: "Smith"; }

// _john2.firstName = ''
// result: Cannot assign to 'firstName' because it is a read-only property.ts(2540)


// ## readonly

interface ReadonlyPerson {
readonly firstName: string;
readonly middleName?: string;
readonly lastName: string;
}

type ReadonlyPerson2 = Readonly<Person>;

Loading

0 comments on commit 9197dab

Please sign in to comment.