From 9197dabaea74b9889888f546417ce688e757f3a7 Mon Sep 17 00:00:00 2001 From: pero5ar Date: Wed, 8 Sep 2021 17:55:54 +0200 Subject: [PATCH] checkpoint 2021-09-07 --- 001.ts | 98 ++++++++++++++++++++++++++++++++++ 002.ts | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ 003.ts | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++ 004.tsx | 101 +++++++++++++++++++++++++++++++++++ 005.ts | 2 + README.md | 10 ++++ package.json | 4 ++ tsconfig.json | 3 +- types101.ts | 53 ------------------- 9 files changed, 502 insertions(+), 54 deletions(-) create mode 100644 001.ts create mode 100644 002.ts create mode 100644 003.ts create mode 100644 004.tsx create mode 100644 005.ts create mode 100644 README.md delete mode 100644 types101.ts diff --git a/001.ts b/001.ts new file mode 100644 index 0000000..9057300 --- /dev/null +++ b/001.ts @@ -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 = { a: 'value' }; +let _obj5: { a: string; } = { a: 'value' }; + +type UnknownObject = Record; +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; +// result: LowercaseHttpVerbs | "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" +// see: Uppercase, Lowercase, Capitalize, Uncapitalize + + +// ## Arrays and tuples + +let _strArr1: string[] = ['val1', 'val2']; +let _strArr2: Array = ['val1', 'val2']; + +let _strOrNumArr1: (number | string)[] = [1, 'val2']; +let _strOrNumArr2: Array = [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']; \ No newline at end of file diff --git a/002.ts b/002.ts new file mode 100644 index 0000000..a084903 --- /dev/null +++ b/002.ts @@ -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 & Partial; + +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?.(); + diff --git a/003.ts b/003.ts new file mode 100644 index 0000000..f362c75 --- /dev/null +++ b/003.ts @@ -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; direction: Direction | null; /* ... */ } +): Promise + + +// ## 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; + diff --git a/004.tsx b/004.tsx new file mode 100644 index 0000000..2963f87 --- /dev/null +++ b/004.tsx @@ -0,0 +1,101 @@ +// # Generics & functions + + +// ## Generics + +function genericFunction(arg: T): T { + return arg; +}; +_number = genericFunction(1); + +class GenericClass { + arg: T; + + constructor(arg: T) { + this.arg = arg + } +}; +let _numberObject = new GenericClass(_number); + +interface GenericInterface { arg: T }; +let _numberObjectAsInterface: GenericInterface = _numberObject; + +type GenericType = { arg: T }; +let _numberObjectAsType: GenericType = _numberObject; + + +// ## Generic Constraints + +function deepCopyObject(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} + +let _numberObjectCopy = deepCopyObject(_numberObject); +// let _numberObjectCopy: GenericClass + +// let _numberCopy = deepCopyObject(_number) +// result: Argument of type 'number' is not assignable to parameter of type 'object'.ts(2345) + + +// ## How to type a class? + +function createObject( + _class: new (...args: CArgs) => T, + _args: CArgs +): T { + return new _class(..._args); +} + +let _numberObject2 = createObject(GenericClass, [1]); +// let _numberObject2: GenericClass + + +// ## Default values & React usage + +import * as React from 'react'; + +interface DropdownProps { + defaultValue?: T; + options: T[]; + onChange: (value: T) => void; +} + +class Dropdown extends React.Component> { + // implementation +} + +let Element = ({ opts1, opts2 }: { opts1: string[], opts2: number[] }) => { + const _onChange1 = React.useCallback((val: string) => console.log(val), []); + // const _onChange1: (val: string) => void + + const _onChange2 = React.useCallback['onChange']>( + (val: number) => console.log(val), + [] + ); + // const _onChange2: (value: number) => void + + return ( + <> + + options={opts2} onChange={_onChange2} /> + + + ) +} + + +// ## Function overloads + +function len(s: string): number; +function len(arr: T[]): number; + +function len(x: string | any[]) { + return x.length; +} + +len('string'); +len([1, 2, 3]); + +len([1, 2, 'random string', [1, 2]]); +// function len(arr: (string | number | number[])[]): number + diff --git a/005.ts b/005.ts new file mode 100644 index 0000000..03d1d3f --- /dev/null +++ b/005.ts @@ -0,0 +1,2 @@ +// # Conditional types + diff --git a/README.md b/README.md new file mode 100644 index 0000000..29361e2 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# typescript-examples +Examples for a typescript lecture. + +Typescript version 4 is expected, but most examples work on props versions as well + +Resources: + - [Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) + - [Release Notes](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html) + - [Playground](https://www.typescriptlang.org/play) -> go to `Examples` or `What's New` + - [tsconfig options](https://www.typescriptlang.org/tsconfig) \ No newline at end of file diff --git a/package.json b/package.json index a2ca1e4..ada926f 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,10 @@ }, "homepage": "https://github.com/pero5ar/typescript-examples#readme", "dependencies": { + "react": "^17.0.2", "typescript": "^4.4.2" + }, + "devDependencies": { + "@types/react": "^17.0.20" } } diff --git a/tsconfig.json b/tsconfig.json index 41e32d9..8544e45 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,9 @@ "compilerOptions": { "module": "commonjs", "target": "es2020", - "jsx": "preserve", + "jsx": "react", "strictFunctionTypes": true, + "strictNullChecks": true, "sourceMap": true }, "exclude": [ diff --git a/types101.ts b/types101.ts deleted file mode 100644 index a94df0a..0000000 --- a/types101.ts +++ /dev/null @@ -1,53 +0,0 @@ -// All the basic types one might need - -// JS Primitives - -let _number: number = 0; -let _string: string = 'str'; -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' }; -let _obj2: {} = { a: 'value' }; -let _obj3: { [key: string]: any; } = { a: 'value' }; -let _obj4: Record = { a: 'value' }; -let _obj5: { a: string; } = { a: 'value' }; - -// Functions - -let _fun1: Function = (x: number) => x++; -let _fun2: (...args: any[]) => any = (x: number) => x++; -let _fun3: (x: any) => any = (x: number) => x++; -let _fun4: (x: number) => number = (x: number) => x++; - -// 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.a; - } - return undefined; -} - -function isObjWithA(obj: unknown): obj is { a: unknown } { - return typeof obj === 'object' && 'a' in obj; -} - -function throwError(msg: string): never { - throw new Error(msg); -} - -// TS Literals - -let _true: true = true; -let _successfulResponse: 200 | 201 | 204 = 200; \ No newline at end of file