-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
502 additions
and
54 deletions.
There are no files selected for viewing
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
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']; |
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
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?.(); | ||
|
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
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>; | ||
|
Oops, something went wrong.