├── 00_0_intro.md ├── 00_1_0_structural-typing.md ├── 01_1_1_type-inference-and-type-annotation.md ├── 01_2_types-string-number-boolean-union.md ├── 01_3_types-null-undefined-void-never-any-unknown.md ├── 01_4_types-object-array-tuple-enum-set.md ├── 01_5_type-alias-interfaces.md ├── 01_6_classes.md ├── 01_7_functions.md ├── 02_0_type-assertions-casting-declaration.md ├── 02_1_type-guards.md ├── 02_2_intersection-types.md ├── 03_0_generics.md └── README.md /00_0_intro.md: -------------------------------------------------------------------------------- 1 | # TypeScript 2 | 3 | 26 | 27 | ## Static and Dynamic languages 28 | 29 | 35 | 36 | TS is static. 37 | 38 | 47 | 48 | ## Setting up our environment 49 | 50 | We are going to install `typescript`, `ts-node` (TS execution and REPL for node.js, with source map support) and `@types/node` (type definitions for Node.js) 51 | 52 | For this you will need to have installed `Node.js` and `NPM` (or `yarn`) 53 | 54 | ```shell 55 | mkdir trying-ts 56 | cd trying-ts/ 57 | 58 | npm init -y 59 | 60 | npm install typescript ts-node @types/node -D 61 | ``` 62 | 63 | Initialize your TS project: 64 | 65 | ```shell 66 | npx tsc --init --rootdir src --outdir lib 67 | ``` 68 | 69 | A new file, `tsconfig.json`, will be created with a standard/default configuration. 70 | From all the options, the most relevant are: 71 | 72 | * target: to which version of JS we are going to "transpile to" 73 | * module: 74 | * rootDir: 75 | * lib: 76 | * sourceMap: 77 | * strict: 78 | * outDir: 79 | * noImplicitAny: 80 | * include and exclude: 81 | 82 | Example of a basic `tsconfig.json` (not the default one): 83 | 84 | ```json 85 | { 86 | "compilerOptions": { 87 | /* Enable incremental compilation */ 88 | "target": "es5", 89 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 90 | "module": "commonjs", 91 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 92 | "strict": true, 93 | /* Enable all strict type-checking options. */ 94 | /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 95 | "esModuleInterop": true, 96 | /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 97 | "skipLibCheck": true, 98 | /* Skip type checking of declaration files. */ 99 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 100 | }, 101 | "include": ["./src/**/*.ts"], 102 | "exclude": ["fileToExclude.ext", "folderToExclude", "node_modules"] // by default node_modules is excluded 103 | } 104 | ``` 105 | 106 | Edit your `package.json` and add a new script: `"build": "npx tsc"` (which is going to *transpile the TS to vanilla JS*). 107 | 108 | Sample result: 109 | ```json 110 | "scripts": { 111 | "build": "tsc", 112 | "dev": "tsc --watch --preserveWatchOutput", 113 | "test": "echo \"Error: no test specified\" && exit 1" 114 | } 115 | ``` 116 | 117 | Note: Here I'm adding a `dev` script which will *watch for changes in our files* and re-compile. 118 | 119 | Create the root dir `src` and a dummy file: 120 | 121 | ```shell 122 | mkdir src 123 | 124 | echo ' 125 | let user: string = `Peter`; 126 | console.log(`Hello ${user}`); 127 | ' > src/index.ts 128 | ``` 129 | 130 | Now, in your terminal, execute: 131 | 132 | ```shell 133 | npm run build 134 | ``` 135 | 136 | After compiling your TS code into JS (so it can be executed by Node and any browser), run your app 137 | 138 | ```shell 139 | node lib/index 140 | ``` 141 | 142 | 143 | 166 | 167 | 168 | 172 | 173 | 176 | 177 | -------------------------------------------------------------------------------- /00_1_0_structural-typing.md: -------------------------------------------------------------------------------- 1 | # Structural Typing 2 | 3 | As you can see, the structure of `Person` and `User` is the same: both have the member `name` 4 | 5 | ```ts 6 | type Person = { name: string } 7 | type User = { name: string } 8 | 9 | let person: Person = { name: 'Peter' }; 10 | let user: User = { name: 'Wendy' }; 11 | ``` 12 | 13 | `Person` and `User` are type compatible. We can assign a person to a user (or vice versa) because they have the same structure: 14 | 15 | ```ts 16 | person = user; 17 | user = person; 18 | ``` 19 | 20 | How, what happens if we add a member to `User`: 21 | 22 | ```ts 23 | type Person = { name: string } 24 | type User = { name: string, id: number } 25 | 26 | let person: Person = { name: 'Peter' }; 27 | let user: User = { name: 'Wendy', id: 123 }; 28 | 29 | person = user; 30 | user = person; // Property 'id' is missing in type 'Person' but required in type 'User'. 31 | ``` 32 | 33 | Now `user` can be assigned to `person` but `person` can NOT be assigned to `user` since it's missing the property `id` 34 | 35 | **KEY:** Extra properties OK, missing properties ERROR. 36 | 37 | -------------------------------------------------------------------------------- /01_1_1_type-inference-and-type-annotation.md: -------------------------------------------------------------------------------- 1 | 2 | # Type Annotation and Type Inference 3 | 4 | --- 5 | 6 | * [Type Annotation or Type Assignment](#type-annotation-or-type-assignment) 7 | * [Type Inference](#type-inference) 8 | + [Const Assertion](#const-assertion) 9 | 10 | --- 11 | 12 | **Preliminar note:** 13 | Usually you are going to let TS infer the type. However, there are cases where you want to add an annotation: 14 | 1. For `functions that return any` (example: `JSON.parse()`) 15 | 2. For when we `declare a variable but initialize it later` 16 | 3. For when the `type cannot be inferred` 17 | 18 | **Example #1** 19 | 20 | ```ts 21 | const parsedObject = JSON.parse('{ "name": "Peter" }'); 22 | ``` 23 | 24 | * If you hover over `parsedObject` you will see that the type is `any` 25 | * If you hover over `.parse()` you will see that the return type is `any` 26 | 27 | ```ts 28 | (method) JSON.parse(text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined): any 29 | ``` 30 | 31 | **Example #1 with annotation** 32 | 33 | ```ts 34 | const parsedObject: { name: string } = JSON.parse('{ "name": "Peter" }'); 35 | ``` 36 | 37 | **Example #2** 38 | 39 | If you hover over `response` you will see that the type is `any` 40 | 41 | ```ts 42 | let response; 43 | response = JSON.parse('{ "name": "Peter" }') 44 | ``` 45 | 46 | **Example #2 with annotation** 47 | 48 | ```ts 49 | let response: { name: string }; 50 | response = JSON.parse('{ "name": "Peter" }') 51 | ``` 52 | 53 | ## Type Annotation or Type Assignment 54 | A label we add to tell TS what is the value type 55 | 56 | ```ts 57 | let age: number; 58 | age = 33; 59 | ``` 60 | 61 | ## Type Inference 62 | TS tries to figure out what's the type of the value 63 | 64 | This is a **literal type** because age will always be 33 65 | ```ts 66 | const age = 33; 67 | ``` 68 | 69 | If we try to change the type of the value to a string... 70 | 71 | ```ts 72 | let age = 33; 73 | age = '33'; 74 | ``` 75 | 76 | .. TS will complain with: `Type 'string' is not assignable to type 'number'.(2322)` 77 | 78 | Since we are initializing the variable with a value,TS expects that the value TYPE is not going to change. 79 | 80 | --- 81 | 82 | Quick note: 83 | 84 | The difference between `let nickname = 'Peter'` and `const nickname = 'Peter'` is... 85 | * For `let` TS expects a type of `string` for the value. 86 | * For `const` TS expects that the value is going to be the string `Peter` 87 | 88 | --- 89 | 90 | If we do the following... 91 | 92 | ```ts 93 | let age; 94 | age = 33; 95 | age = '33'; 96 | ``` 97 | 98 | ... it will not complain given that we are` escaping TS inference` (and, the type of age is going to be any). 99 | 100 | ### Const Assertion 101 | Can be used in any value 102 | 103 | 107 | 108 | Declaring the variable `user` as a constant (const) protects its value from been changed. 109 | 110 | ```ts 111 | const user = { 112 | name: 'Peter', 113 | age: 33 114 | } 115 | 116 | user = 'Peter'; 117 | // Cannot assign to 'user' because it is a constant. 118 | ``` 119 | 120 | However, we can manipulate the object per se: 121 | 122 | ```ts 123 | user.name = 'Wendy'; 124 | console.log(user); 125 | // { "name": "Wendy", "age": 33 } 126 | ``` 127 | 128 | 129 | If we want to make the user object immuread-only at compilation timetable, we can use `const assertion` 130 | 131 | ```ts 132 | const user = { 133 | name: 'Peter', 134 | age: 33 135 | } as const 136 | 137 | user.name = 'Wendy'; 138 | // Cannot assign to 'name' because it is a read-only property. 139 | ``` 140 | 141 | What does `const assertion` do...? 142 | 1. Makes all members read only 143 | 2. Converts primitives to literal types (so, for example, name con only hold the string "Peter") 144 | 3. Converts arrays to read only tupples 145 | 146 | -------------------------------------------------------------------------------- /01_2_types-string-number-boolean-union.md: -------------------------------------------------------------------------------- 1 | 2 | # Types: String, Number, Boolean and Union Types 3 | 4 | --- 5 | 6 | * [String](#string) 7 | * [Number](#number) 8 | * [Boolean](#boolean) 9 | * [Union Types (aka, OR)](#union-types) 10 | + [Discriminated unions](#discriminated-unions) 11 | * [Intersection Types (aka, AND)](#intersection-types) 12 | 13 | --- 14 | 15 | ## String 16 | 17 | ```ts 18 | let name: string; 19 | name = 'Peter'; 20 | ``` 21 | 22 | ## Number 23 | Including ALL integers (-33, 33) and float (3.3) 24 | 25 | ```ts 26 | let age: number; 27 | age = 33; 28 | ``` 29 | 30 | ## Boolean 31 | 32 | ```ts 33 | let completed: boolean; 34 | completed = false; 35 | ``` 36 | 37 | ## Union Types 38 | (aka, OR) 39 | When more than one type can be used. 40 | 41 | ```ts 42 | let zipCode: number | string; 43 | zipCode = 94115; 44 | zipCode = '94115'; 45 | ``` 46 | 47 | We can narrow with type guards (known also as `type narrowing`): 48 | 49 | 1. === or !=== 50 | 2. typeof 51 | 3. instaceof 52 | 4. property in object 53 | 54 | Example: 55 | 56 | ```ts 57 | function parseZipCode(zip: number | string) { 58 | if (typeof zip !== 'string') return zip.toString(); 59 | return zip; 60 | } 61 | 62 | console.log(parseZipCode(94115)); // "94115" 63 | console.log(parseZipCode('94115')); // "94115" 64 | ``` 65 | 66 | 70 | 71 | ### Discriminated unions 72 | 73 | Taking the previous example, we add a property (example: `kind`) to all the types in the union (in this case `Cat` and `Fish`) 74 | Then, we check the value of `kind` 75 | 76 | ```ts 77 | type Animal = Cat | Fish; 78 | 79 | type Cat = { 80 | kind: 'cat', 81 | walk: Function 82 | } 83 | 84 | type Fish = { 85 | kind: 'fish' 86 | swim: Function 87 | } 88 | 89 | const cat: Cat = { 90 | kind: 'cat', 91 | walk: function(): void { 92 | console.log(`walking`); 93 | } 94 | } 95 | 96 | const fish: Fish = { 97 | kind: 'fish', 98 | swim: function(): void { 99 | console.log(`swimming`); 100 | } 101 | } 102 | 103 | function move(animal: Animal) { 104 | if (animal.kind === 'cat') { 105 | animal.walk(); 106 | } 107 | if (animal.kind === 'fish') { 108 | animal.swim(); 109 | } 110 | } 111 | 112 | move(cat); // walking 113 | move(fish); // swimming 114 | ``` 115 | 116 | ## Intersection Types 117 | (aka, AND) 118 | 119 | ```ts 120 | let user: { name: string; age: number } & { username: string } 121 | 122 | user = { 123 | name: 'Peter', 124 | age: 33, 125 | username: 'peter' 126 | } 127 | ``` 128 | 129 | If we try to define a user without username, TS will complain with: 130 | 131 | ``` 132 | Type '{ name: string; age: number; }' is not assignable to type '{ name: string; age: number; } & { username: string; }'. 133 | Property 'username' is missing in type '{ name: string; age: number; }' but required in type '{ username: string; }'. 134 | ``` -------------------------------------------------------------------------------- /01_3_types-null-undefined-void-never-any-unknown.md: -------------------------------------------------------------------------------- 1 | # Types: Null, Undefined, Void, Never, Any and Unknown 2 | 3 | 6 | 7 | --- 8 | 9 | * [Null](#null) 10 | * [Undefined](#undefined) 11 | * [Void](#void) 12 | * [Never](#never) 13 | * [Any](#any) 14 | * [Unknown](#unknown) 15 | 16 | 17 | --- 18 | 19 | ## Null 20 | For when a variable is not yet declared 21 | 22 | ```ts 23 | const getUppercasedLetters = (str: string): string[] | null => { 24 | const names = str.match(/[A-Z]/g); 25 | return names; 26 | } 27 | 28 | console.log( 29 | getUppercasedLetters('Hi1How2Are3you'); 30 | ) 31 | // ["H", "H", "A"] 32 | 33 | console.log( 34 | getUppercasedLetters(''); 35 | ) 36 | // null 37 | ``` 38 | 39 | ## Undefined 40 | For a variable is declared but not defined 41 | 42 | ```ts 43 | const sayHi = (name: string | undefined): void => { 44 | if (name === undefined) { 45 | console.log(`Value was undefined`); 46 | } 47 | } 48 | 49 | sayHi('Peter'); 50 | sayHi(undefined); 51 | ``` 52 | 53 | We can use `==` instead of `===` to check for `null` or `undefined`. 54 | 55 | Remember: 56 | 57 | ```js 58 | console.log(null == undefined); // true 59 | console.log(null === undefined); // false 60 | ``` 61 | 62 | Example: 63 | 64 | ```ts 65 | const sayHi = (name: string | undefined | null): void => { 66 | if (name == undefined) { 67 | console.log(`Value was undefined or null`); 68 | } 69 | } 70 | 71 | sayHi('Peter'); 72 | sayHi(null); // Value was undefined or null 73 | sayHi(undefined); // Value was undefined or null 74 | ``` 75 | 76 | ## Void 77 | 78 | For when a function does `NOT return`. For example, logging to the console, using the alert() window method, etc. 79 | 80 | ```ts 81 | const sayHi = (name: string): void => { 82 | console.log(name); 83 | } 84 | 85 | sayHi('Peter'); 86 | }; 87 | ``` 88 | 89 | ## Never 90 | 91 | For functions that will never "return anything". 92 | Examples: `infinite loops` and `throwing errors`. 93 | 94 | ```ts 95 | const formatError = (err: string): never => { 96 | throw new Error(`We found the following error: ${err}`); 97 | } 98 | 99 | console.log( 100 | formatError('ERROR 404') 101 | ) 102 | 103 | 104 | const infiniteLoop = (): never => { 105 | while (true) { 106 | console.log(1) 107 | } 108 | } 109 | ``` 110 | 111 | ## Any 112 | 113 | You should **AVOID** the use of `any` (it is the same as not using typing; it defeats the purpose of TS). Instead, use `unknown` if possible. 114 | For when we don't know the type of data that a value will hold. 115 | 116 | Both, `any` and `unknown` can hold any type. 117 | 118 | ```ts 119 | let something: any 120 | 121 | something = 1; 122 | something = '1'; 123 | something = () => 1; 124 | 125 | // TS will not complain when accessing non existent properties if we use any 126 | something.property.to.access; 127 | ``` 128 | 129 | ## Unknown 130 | 131 | For when we don't know the type of data but we want to keep the convenience of the type system. 132 | 133 | ```ts 134 | let something: unknown 135 | 136 | something = 1; 137 | something = '1'; 138 | something = () => 1; 139 | ``` 140 | 141 | The following will not work with unknown (unless we use a type guard) 142 | 143 | ```ts 144 | something.property.to.acccess; 145 | ``` 146 | 147 | TS will complain with `Object is of type 'unknown'.` 148 | 149 | To prevent the error, we have to use `type guards` and check its type: 150 | 151 | ```ts 152 | let something: unknown 153 | something = {}; 154 | something = '1'; 155 | something = 1; 156 | 157 | 158 | if (typeof something === 'number') { 159 | console.log(something.toFixed()); // 1 160 | } 161 | else console.log('Not a number'); 162 | ``` 163 | 164 | -------------------------------------------------------------------------------- /01_4_types-object-array-tuple-enum-set.md: -------------------------------------------------------------------------------- 1 | # Types: Object, Array, Enum, Tuple and Set 2 | 3 | --- 4 | 5 | * [Object (object literal)](#object) 6 | + [Optional modifier (?)](#optional-modifier) 7 | + [Non-null assertion operator (!)](#non-null-assertion-operator) 8 | * [Array](#array) 9 | * [Enum](#enum) 10 | * [Tuple](#tuple) 11 | * [Set](#set) 12 | 13 | --- 14 | 15 | ## Object 16 | (object literal) 17 | 18 | ```ts 19 | const user: { name: string; age: number } = { 20 | name: 'Peter', 21 | age: 35 22 | } 23 | ``` 24 | 25 | For simple object literals, we can let TS infer its type. 26 | 27 | ```ts 28 | const user = { 29 | name: 'Peter', 30 | age: 35 31 | } 32 | ``` 33 | 34 | When we are **destructuring**, we will not need to annotate the value's type (TS will infer it for us), yet, if we want to do it, this is the proper way to do it: 35 | 36 | ```ts 37 | const user = { 38 | name: 'Peter', 39 | age: 35 40 | } 41 | 42 | const { name, age } : { name: string; age: number } = user; 43 | ``` 44 | 45 | ### Optional modifier 46 | (aka, ?) 47 | 48 | For optional properties we can use `property?` 49 | 50 | ```ts 51 | const user: { name: string; age: number; nickname?: string } = { 52 | name: 'Peter', 53 | age: 35 54 | } 55 | ``` 56 | 57 | ### Non-null assertion operator 58 | (aka, !) 59 | 60 | In the following example we declare a variable and sets its type as a string. 61 | Then, we have a function that generates a random character. 62 | We invoke our function and the generated character is assigned to the variable. 63 | However, if we log the value of the variable character, TS will error with: `Variable 'character' is used before being assigned.` 64 | 65 | ```ts 66 | let character: string; 67 | 68 | function generateCharacter() { 69 | let characters: Array = ['Wendy', 'Peter', 'Hook']; 70 | character = characters[Math.floor(Math.random() * ((characters.length) - 0) + 0)]; 71 | } 72 | 73 | generateCharacter(); 74 | console.log(character); // Peter 75 | ``` 76 | 77 | TS doesn't track the side effect (mutation) so, for the compiler, the value of character is still `undefined` 78 | 79 | We can use the `non-null assertion operator` to tell the compiler that character is going to have a value. 80 | 81 | ```ts 82 | console.log(character!); // Peter 83 | ``` 84 | 85 | We can also use it during declaration (this is called `definite assignment assertion`) 86 | 87 | ```ts 88 | let character!: string; 89 | ``` 90 | 91 | **Index signature** 92 | 93 | 97 | 98 | The types of the key can only be `string` or `number`. 99 | 100 | ```ts 101 | type User = { 102 | name: string, 103 | age: number 104 | } 105 | 106 | type UserDictionary = { 107 | [name: string]: User | undefined 108 | } 109 | 110 | const users: UserDictionary = { 111 | peter: { name: 'Peter', age: 33 }, 112 | paul: { name: 'Paul', age: 34 }, 113 | mike: { name: 'Mike', age: 35 } 114 | } 115 | 116 | console.log(users['paul']); // { name: 'Paul', age: 34 } 117 | ``` 118 | 119 | We use `User | undefined` to let TS know that we could try to access to a missing property of the object. 120 | 121 | For example, if we initialize users as an empty object. 122 | 123 | ```ts 124 | const users: UserDictionary = {} 125 | console.log(users['paul'].name); 126 | ``` 127 | 128 | Without `undefined` TS will not complain. However, as soon as we add `undefined`, the same code will error with: 129 | 130 | ``` 131 | Object is possibly 'undefined'. 132 | ``` 133 | 134 | ## Array 135 | 136 | Remember: we are NOT using `inference` since we are declaring the variables first and then initializing them. 137 | 138 | 1. Array of strings `string[]`: 139 | 140 | ```ts 141 | let arr1: string[]; 142 | 143 | arr1 = ['hi', 'hello']; 144 | ``` 145 | 146 | 2. Array of numbers `number[]`: 147 | 148 | ```ts 149 | let arr2: number[]; 150 | 151 | arr2 = [-2,0,1]; 152 | ``` 153 | 154 | 3. Array containing mixed types with OR (`|`). 155 | 156 | ```ts 157 | let numbersAndStrings: (number | string)[] = []; 158 | 159 | numbersAndStrings = [1,'hi']; 160 | ``` 161 | 162 | 4. Array containing ANY type 163 | (Usually you want to avoid `any` since the idea is to set "strict" types) 164 | 165 | ```ts 166 | let arr3: any[]; 167 | 168 | arr3 = [1,true,'hi',[]]; 169 | ``` 170 | 171 | 5. Array containing an `array of x-type` or multidimensional arrays. 172 | ```ts 173 | let numbers: number[][]; 174 | 175 | numbers = [ 176 | [1,2,3], 177 | [4,5,6] 178 | ]; 179 | ``` 180 | 181 | 6. Array containing an `object type` 182 | 183 | ```ts 184 | let users: { name: string, age: number }[]; 185 | 186 | users = [ 187 | { name: 'Peter', age: 33 }, 188 | { name: 'Paul', age: 34 }, 189 | { name: 'Mike', age: 35 } 190 | ]; 191 | ``` 192 | 193 | When we pull elements from an `array` we are going to let TS infer its type: 194 | 195 | ```ts 196 | const peter = users[0].name; 197 | ``` 198 | 199 | In the previous example, since TS knows that `users` is an array of object with 2 properties name (string) and age (number) it will infer that the type of `peter` is string. 200 | 201 | Arrays support `readonly`: 202 | 203 | ```ts 204 | type NonMutableArray = readonly number[]; 205 | 206 | const arr: NonMutableArray = [1, 2, 3]; 207 | 208 | console.log(arr.reverse()); // [3, 2, 1] 209 | 210 | console.log(arr.slice().reverse()); // [1, 2, 3] 211 | ``` 212 | 213 | In this example, when we try to reverse our array, TS complain with: 214 | 215 | ``` 216 | Property 'reverse' does not exist on type 'NonMutableArray'. 217 | ``` 218 | 219 | However, as soon as we create a shallow copy of our array with `slice()` we can reverse the NEW array. 220 | 221 | ## Enum 222 | We assign names to a set of numeric values. 223 | 224 | ```ts 225 | enum Role { 226 | USER, 227 | EDITOR, 228 | ADMIN 229 | } 230 | 231 | let user1: Role = Role.ADMIN; 232 | 233 | console.log(user1); // 2 234 | ``` 235 | 236 | In this example... 237 | * USER is 0 238 | * EDITOR is 1 239 | * ADMIN is 2 240 | 241 | ```ts 242 | const role: string = Role[1] 243 | 244 | console.log(role) // EDITOR 245 | ``` 246 | 247 | We can start the set with a default value... 248 | ```ts 249 | enum Role { 250 | USER = 20, 251 | EDITOR, 252 | ADMIN 253 | } 254 | ``` 255 | In this example... 256 | * USER is 20 257 | * EDITOR is 21 258 | * ADMIN is 22 259 | 260 | Or, we can even default the values of each number of the set. 261 | 262 | ```ts 263 | enum Role { 264 | USER = 20, 265 | EDITOR = 30, 266 | ADMIN = 40 267 | } 268 | ``` 269 | 270 | ## Tuple 271 | An array with fixed length and known types. 272 | 273 | ```ts 274 | let tuple: [number, string, boolean]; 275 | 276 | tuple = [1,'string',false]; 277 | 278 | console.log(tuple); // [ 1, 'string', false ] 279 | ``` 280 | 281 | Result: `[ 1, 'string', false ]` 282 | 283 | If we provide an extra element... 284 | 285 | ```ts 286 | let tuple: [number, string, boolean]; 287 | 288 | tuple = [1,'string',false, 1]; 289 | 290 | ``` 291 | ... TS will complain: 292 | 293 | ``` 294 | Type '[number, string, false, number]' is not assignable to type '[number, string, boolean]'. 295 | Source has 4 element(s) but target allows only 3. 296 | ``` 297 | 298 | **HOWEVER**, this only works on assignment, we can push/pop without any error/warning: 299 | 300 | ```ts 301 | let tuple: [number, string, boolean]; 302 | 303 | tuple = [1,'string',false]; 304 | tuple.push(1); 305 | 306 | console.log(tuple); // [1, "string", false, 1] 307 | ``` 308 | 309 | If we try to assign a `string` as the first element of the previous tuple... 310 | 311 | ```ts 312 | tuple[0] = 'hi'; 313 | ``` 314 | 315 | ... TS will error, in this case, with the following message: `Type 'string' is not assignable to type 'number'.` 316 | 317 | We can overcome this issue using `readonly`. 318 | 319 | **Important:** This JUST works at compilation time, during runtime the tuple (aka, array) is going to be mutated `[1, "string", false, 1] ` 320 | 321 | The same example: 322 | 323 | ```ts 324 | let tuple: readonly [number, string, boolean]; 325 | 326 | tuple = [1,'string',false]; 327 | tuple.push(1); 328 | 329 | console.log(tuple); // [1, "string", false, 1] 330 | ``` 331 | 332 | ... will make the compiler error with: 333 | 334 | ``` 335 | Property 'push' does not exist on type 'readonly [number, string, boolean]'. 336 | ``` 337 | 338 | ## Set 339 | 340 | 345 | 346 | ```ts 347 | let set: Set 348 | set = new Set(['a', 'b', 'a', 'a', 'a']); 349 | 350 | console.log(set); // Set (2) {"a", "b"} 351 | 352 | console.log(Array.from(set)[0]); // 'a' 353 | ``` -------------------------------------------------------------------------------- /01_5_type-alias-interfaces.md: -------------------------------------------------------------------------------- 1 | # Type Alias and Interfaces 2 | 3 | --- 4 | * [Type Alias](#type-alias) 5 | + [Implements](#implements) 6 | + [Utility types](#utility-types) 7 | - [Readonly](#readonly) 8 | - [Required](#required) 9 | - [Record](#record) 10 | - [Partial](#partial) 11 | * [Interfaces](#interfaces) 12 | + [Extends](#extends) 13 | + [Implements](#implements-1) 14 | --- 15 | 16 | For both, the convention is to use TitleCase. 17 | Both, Type Alias and Interfaces support `readonly` properties (value cannot be changed after initialized) 18 | 19 | **Differences between `Types Alias` and `Interfaces`** 20 | 21 | **Type Alias** 22 | Supports: 23 | * Unions (|) 24 | * Intersections (&) 25 | * Primitives 26 | * Shorthand functions 27 | * Advanced type functions (like conditional types) 28 | 29 | Shorthand function example: 30 | 31 | ```ts 32 | type Greet = (s: string) => string; 33 | 34 | type Human = { 35 | name: string, 36 | greet: Greet 37 | } 38 | 39 | const human: Human = { 40 | name: 'Peter', 41 | greet: (s) => s 42 | } 43 | 44 | console.log( 45 | human.greet(`Hi`) 46 | ); // Hi 47 | ``` 48 | 49 | **Interfaces** 50 | * Declaration merging 51 | * Familiarity (extends) 52 | 53 | 54 | ## Type Alias 55 | 56 | Is how we name our own types 57 | We can declare types in one place and import/export them. 58 | 59 | ```ts 60 | type User = { 61 | name: string; 62 | age: number; 63 | } 64 | 65 | const users: User[] = [ 66 | { name: 'Peter', age: 33 }, 67 | { name: 'Paul', age: 34 } 68 | ]; 69 | ``` 70 | 71 | ### Implements 72 | 73 | ```ts 74 | type Human = { 75 | eat(): void 76 | } 77 | 78 | class Engineer implements Human { 79 | eat() { 80 | console.log('Eating'); 81 | } 82 | } 83 | 84 | new Engineer().eat(); // Eating 85 | ``` 86 | 87 | ### Utility types 88 | 89 | #### Readonly 90 | (Readonly) 91 | 92 | ```ts 93 | type User = { 94 | name: string, 95 | age: number 96 | } 97 | 98 | const user: Readonly = { 99 | name: 'Peter', 100 | age: 3 101 | } 102 | ``` 103 | 104 | If you try to modify the value of `age`, TS will error with: 105 | 106 | ``` 107 | Cannot assign to 'name' because it is a read-only property. 108 | ``` 109 | 110 | #### Required 111 | (Required) 112 | 113 | 116 | 117 | #### Record 118 | (Record) 119 | 120 | 125 | 126 | If we avoid passing either of the properties `name` or `age`, TS will error. 127 | Example: 128 | 129 | ``` 130 | Type '{}' is missing the following properties from type '{ name: string; age: number; }': name, age 131 | ``` 132 | 133 | Example: 134 | ```ts 135 | type User = Record 136 | 137 | const users: User = { 138 | 'peter': { name: 'Peter', age: 30 }, 139 | 'wendy': { name: 'Wendy', age: 31 } 140 | } 141 | ``` 142 | 143 | #### Partial 144 | (Partial) 145 | 146 | In the followinbg example, we are updating a user just passing the properties that will be used. 147 | (This plays pretty well at the time of updating state) 148 | 149 | ```ts 150 | type UserType = { 151 | name: string, 152 | age: number 153 | } 154 | 155 | // const user: User = { 156 | // name: 'Peter', 157 | // age: 30 158 | // } 159 | 160 | class User { 161 | constructor(public currentState: T) {} 162 | update(nextState: Partial) { 163 | this.currentState = { ...this.currentState, ...nextState } 164 | } 165 | } 166 | 167 | const user = new User({ name:'Peter', age: 30 }); 168 | 169 | user.update({ age: 31 }); 170 | 171 | console.log(user); 172 | 173 | // [LOG]: User: { 174 | // "currentState": { 175 | // "name": "Peter", 176 | // "age": 31 177 | // } 178 | // } 179 | ``` 180 | 181 | ## Interfaces 182 | 183 | Used to define an object type. 184 | 185 | 193 | 194 | 198 | 199 | 200 | As with `Type Alias`, we abstract the type annotation, so instead of doing this... 201 | 202 | ```ts 203 | const logUser = (user: { name: string; age: number }): void => { 204 | // since we are not returning we use void 205 | console.log(user.name, user.age); 206 | } 207 | ``` 208 | 209 | ... we do this ... 210 | 211 | ```ts 212 | interface User { 213 | name: string; 214 | age: number; 215 | introduce(): string; 216 | } 217 | 218 | const myUser = { 219 | name: 'Peter', 220 | age: 35, 221 | lastname: 'Pan', 222 | introduce() { 223 | return `Hi, I'm ${this.name}`; 224 | } 225 | } 226 | 227 | const logUser = (user: User): void => { 228 | // since we are not returning we use void 229 | console.log(user.name, user.age); // "Peter", 35 230 | 231 | console.log(user.introduce()); // "Hi, I'm Peter" 232 | } 233 | 234 | logUser(myUser); 235 | ``` 236 | 237 | The object we are passing must have ALL the properties declared in our interface (and the right types); we can have more properties than the ones we have in our `interface`, but **not less**. 238 | 239 | **Interfaces are open**... We can have multiple declarations in the same scope. 240 | TS is going to merge the declarations of the interfaces into a single type (this is called `Interface Declaration Merging`). This is not supported by `Types Alias` 241 | 242 | ```ts 243 | interface IHuman { 244 | eat(): void 245 | } 246 | 247 | interface IHuman { 248 | isEmployed(): boolean 249 | } 250 | ``` 251 | 252 | So, we can augment existing objects... For example, `Window` 253 | 254 | ```ts 255 | interface Window { 256 | someMethod(): number 257 | } 258 | 259 | window.someMethod; 260 | ``` 261 | 262 | **Interfaces (as Classes) support optional properties** 263 | 264 | ```ts 265 | interface IPerson { 266 | name: string; 267 | hobbies?: string[] 268 | } 269 | 270 | const person: IPerson = { 271 | name: 'Peter', 272 | } 273 | ``` 274 | 275 | ### Extends 276 | 277 | Interfaces can extend from other interfaces (like classes) 278 | 279 | In the following example, TS is going to require that `engineer` has both methods `eat()` and `isEmployed()` returning the proper value type. 280 | 281 | ```ts 282 | interface Human { 283 | eat(): void 284 | } 285 | 286 | interface Engineer extends Human { 287 | isEmployed(): boolean 288 | } 289 | 290 | const engineer: Engineer = { 291 | eat: () => console.log('Eating'), 292 | isEmployed: () => true 293 | } 294 | ``` 295 | 296 | ### Implements 297 | 298 | Everything we have on the interface should be implemented in the class. 299 | 300 | ```ts 301 | interface IHuman { 302 | eat(): void 303 | } 304 | 305 | interface IEngineer extends IHuman { 306 | isEmployed(): boolean 307 | } 308 | 309 | class Engineer implements IEngineer { 310 | eat() { 311 | console.log('Eating'); 312 | } 313 | 314 | isEmployed() { 315 | return true; 316 | } 317 | } 318 | ``` 319 | 320 | We can implement as many interfaces as we want 321 | 322 | ```ts 323 | class myClass implements IOne, ITwo { } 324 | ``` 325 | 326 | 327 | **Read only property example** 328 | 329 | In the following example, we are stating that `myUser` is going to hold a an object with 2 properties: `name` (which should be immutable) and `age`. 330 | If we try to change the value of name, TS will error: `Cannot assign to 'name' because it is a read-only property.` 331 | 332 | ```ts 333 | interface IUser { 334 | readonly name: string; 335 | age: number; 336 | } 337 | 338 | const myUser: IUser = { 339 | name: 'Peter', 340 | age: 35 341 | } 342 | 343 | myUser.name = 'Wendy'; 344 | ``` 345 | 346 | Another example but with a `class`. 347 | In this case, we first tell the compiler that `user` is going to hold a type of `IUser` 348 | Then we initialize a new `User`. 349 | Then we try to mutate the value of the property name. TS will complain as before: `Cannot assign to 'name' because it is a read-only property.` 350 | 351 | ```ts 352 | interface IUser { 353 | readonly name: string; 354 | age: number; 355 | } 356 | 357 | class User implements IUser { 358 | name: string; 359 | age: number; 360 | 361 | constructor(n: string, age: number) { 362 | this.name = n; 363 | this.age = age; 364 | } 365 | } 366 | 367 | let user: IUser; 368 | user = new User('Peter', 33); 369 | 370 | 371 | user.name = 'Wendy'; 372 | ``` -------------------------------------------------------------------------------- /01_6_classes.md: -------------------------------------------------------------------------------- 1 | # Classes and Inheritance 2 | 3 | --- 4 | 5 | * [Classes](#classes) 6 | + [Access modifier keywords](#access-modifier-keywords) 7 | + [Param properties](#param-properties) 8 | * [Inheritance](#inheritance) 9 | + [Abstract classes](#abstract-classes) 10 | + [Static members](#static-members) 11 | + [Private Constructors](#private-constructors) 12 | 13 | --- 14 | 15 | ## Classes 16 | 17 | 26 | 27 | **Class in vanilla JS** 28 | 29 | ```js 30 | class Person { 31 | constructor(name, age) { 32 | this.name = name 33 | this.age = age 34 | } 35 | 36 | logMessage(message) { 37 | console.log(message); 38 | } 39 | } 40 | 41 | const peter = new Person('Peter', 30); 42 | peter.logMessage(`Hi`); // Hi 43 | ``` 44 | 45 | **Class in TS** 46 | 47 | ```ts 48 | class Person { 49 | 50 | name: string; 51 | age: number; 52 | 53 | constructor(name: string, age: number) { 54 | this.name = name 55 | this.age = age 56 | } 57 | 58 | logMessage(message: string): void { 59 | console.log(message); 60 | } 61 | } 62 | 63 | const peter = new Person('Peter', 30); 64 | peter.logMessage(`Hi`); // Hi 65 | ``` 66 | 67 | **Classes (as Interfaces) support optional properties** 68 | 69 | In the following example we are setting `hobbies property` (or field) as optional. 70 | Then, we are setting `hobbies parameter` in the constructor as optional. 71 | 72 | 73 | ```ts 74 | class Person { 75 | 76 | name: string; 77 | hobbies?: string[]; 78 | 79 | constructor(name: string, hobbies?: string[]) { 80 | this.name = name 81 | this.hobbies = hobbies 82 | } 83 | } 84 | 85 | const peter = new Person('Peter'); 86 | ``` 87 | 88 | ### Access modifier keywords 89 | We use these to set the visibility of properties (fields) and methods 90 | Supported for class properties and methods. 91 | 92 | * public -> everyone (default) 93 | * protected -> the own class and the subclasses 94 | * private -> the own class 95 | 96 | * readonly -> when we want to prevent re-assigning values (aka, change after initialization) 97 | 98 | **Public and Private example** 99 | 100 | In our previous example, we can access to the field age (from outside the class) and change it: 101 | 102 | ```ts 103 | class Person { 104 | 105 | name: string; 106 | age: number; 107 | 108 | constructor(name: string, age: number) { 109 | this.name = name 110 | this.age = age 111 | } 112 | } 113 | 114 | const peter = new Person('Peter', 30); 115 | 116 | peter.age = 90; 117 | 118 | console.log(peter); 119 | // { 120 | // "name": "Peter", 121 | // "age": 90 122 | // } 123 | ``` 124 | 125 | To avoid this we can set the field `age` as `private` (while `name` is using the default access modifier, `public`) and add 2 methods: one to set the age and one to get the age: 126 | 127 | ```ts 128 | class Person { 129 | 130 | name: string; 131 | private age: number; 132 | 133 | constructor(name: string, age: number) { 134 | this.name = name 135 | this.age = age 136 | } 137 | 138 | setAge(age: number) { 139 | this.age = age; 140 | } 141 | 142 | getAge() { 143 | return this.age; 144 | } 145 | } 146 | 147 | const peter = new Person('Peter', 30); 148 | peter.setAge(80); 149 | console.log(peter); 150 | 151 | // { 152 | // "name": "Peter", 153 | // "age": 80 154 | // } 155 | 156 | console.log(peter.getAge()); 157 | // 80 158 | ``` 159 | 160 | If you try to modify the value of `age` outside the class ... 161 | 162 | ```ts 163 | peter.age = 90; 164 | ``` 165 | 166 | ... or, if you try to just access (read) the field directly ... 167 | 168 | ```ts 169 | console.log(peter.age); 170 | ``` 171 | 172 | ... TS will error with: `Property 'age' is private and only accessible within class 'Person'.` 173 | 174 | **ES2022** allows us to use a hash prefix `#` for private fields of a class in `JavaScipt` 175 | 176 | Quick note: public, private and protected access modifiers are not supported by `JS` but by `TS` 177 | 178 | If you transpile (to `JS`) the following class with the `age` property marked as private... 179 | 180 | ```ts 181 | class Person { 182 | 183 | private name: string; 184 | 185 | constructor(name: string) { 186 | this.name = name 187 | } 188 | } 189 | 190 | console.log(new Person('Peter').name); // Peter 191 | ``` 192 | 193 | ... this is going to be the result: 194 | 195 | ```js 196 | "use strict"; 197 | class Person { 198 | constructor(name) { 199 | this.name = name; 200 | } 201 | } 202 | console.log(new Person('Peter').name); // Peter 203 | ``` 204 | 205 | The `property age` is accessible in `JavaScript` 206 | 207 | If we want to make it `private` for `JS` too, we can use `#` 208 | 209 | ```ts 210 | class Person { 211 | 212 | #name: string; 213 | 214 | constructor(name: string) { 215 | this.#name = name 216 | } 217 | } 218 | console.log(new Person('Peter').#name); 219 | ``` 220 | 221 | Transpiled output: 222 | 223 | ```js 224 | "use strict"; 225 | var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { 226 | if (kind === "m") throw new TypeError("Private method is not writable"); 227 | if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); 228 | if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); 229 | return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; 230 | }; 231 | var _Person_name; 232 | class Person { 233 | constructor(name) { 234 | _Person_name.set(this, void 0); 235 | __classPrivateFieldSet(this, _Person_name, name, "f"); 236 | } 237 | } 238 | _Person_name = new WeakMap(); 239 | console.log(new Person('Peter').); 240 | ``` 241 | 242 | If you run it: 243 | 244 | ```js 245 | console.log(new Person('Peter').); 246 | SyntaxError: Unexpected token ')' 247 | ``` 248 | 249 | Check the last line `console.log(new Person('Peter').);` 250 | The property `#name` is excluded (in `ES2022`, it is uncluded and supported in `ESNext`) 251 | 252 | **Protected example** 253 | 254 | In this example we are setting `age` as `protected`. 255 | This means that age is ONLY going to be accessible within the class and its subclasses. 256 | 257 | 1. When we try to access the property `age` `outside the class` (or its subclasses), we see the expected TS error: Property 'age' is protected and only accessible within class 'Person' and its subclasses.(2445) 258 | 259 | 2. We can access to the property `age` from a `subclass` (Engineer) without any issue. 260 | 261 | ```ts 262 | class Person { 263 | 264 | name: string; 265 | protected age: number; 266 | 267 | constructor(name: string, age: number) { 268 | this.name = name 269 | this.age = age 270 | } 271 | } 272 | 273 | const peter = new Person('Peter', 30); 274 | console.log(peter.age); 275 | // Property 'age' is protected and only accessible within class 'Person' and its subclasses.(2445) 276 | 277 | 278 | class Engineer extends Person { 279 | 280 | salary: number; 281 | 282 | constructor(name: string, age: number, salary: number) { 283 | super(name, age); 284 | 285 | this.salary = salary; 286 | } 287 | 288 | showAge(): number { 289 | return this.age; 290 | } 291 | } 292 | 293 | const engineer1 = new Engineer('Paul', 33, 100); 294 | 295 | 296 | console.log(engineer1); 297 | 298 | // { 299 | // "name": "Paul", 300 | // "age": 33, 301 | // "salary": 100 302 | // } 303 | 304 | console.log(engineer1.showAge()); // 33 305 | ``` 306 | 307 | 311 | 312 | You can use `_` to mark a property or methods as `protected`, however, the transpiled JS is going to ignore it (at difference of what occurred with `#`) 313 | 314 | Example: 315 | 316 | ```ts 317 | class Person { 318 | 319 | _name: string; 320 | 321 | constructor(name: string) { 322 | this._name = name 323 | } 324 | } 325 | console.log(new Person('Peter')._name); 326 | ``` 327 | 328 | Transpiled code: 329 | 330 | ```js 331 | "use strict"; 332 | class Person { 333 | constructor(name) { 334 | this._name = name; 335 | } 336 | } 337 | console.log(new Person('Peter')._name); // Peter 338 | ``` 339 | 340 | **Read only example** 341 | 342 | In the following example, you can access the field directly (and change its value) but TS will complain since it is set as `readonly`: 343 | `Cannot assign to 'id' because it is a read-only property.` 344 | 345 | ```ts 346 | class Person { 347 | 348 | name: string; 349 | age: number; 350 | readonly id: number; 351 | 352 | constructor(id: number, name: string, age: number) { 353 | this.name = name 354 | this.age = age 355 | this.id = id; 356 | } 357 | } 358 | 359 | const peter = new Person(new Date().getTime(), 'Peter', 30); 360 | 361 | 362 | peter.id = 456; 363 | console.log(peter); 364 | // { 365 | // "name": "Peter", 366 | // "age": 30, 367 | // "id": 456 368 | // } 369 | ``` 370 | 371 | We can use it in combination with `private` (and others access modifiers) 372 | 373 | ```ts 374 | private readonly id: number; 375 | ``` 376 | 377 | ### Param properties 378 | 379 | We can make our previous code conciser using class parameter properties: 380 | 381 | ```ts 382 | class Person { 383 | constructor( 384 | public name: string, 385 | private age: number) { 386 | } 387 | } 388 | 389 | const peter = new Person('Peter', 30); 390 | ``` 391 | 392 | As with `types`, we can set a property as optional with `property?`(example: `private age?: number`) 393 | 394 | ## Inheritance 395 | 396 | An example of inheritance and getters/setters 397 | 398 | ```ts 399 | class Person { 400 | constructor(private name: string) {} 401 | 402 | get getName() { 403 | return this.name; 404 | } 405 | 406 | set setName(name: string) { 407 | if (!name) throw new Error('Name cannot be empty.'); 408 | this.name = name; 409 | } 410 | } 411 | 412 | class Engineer extends Person { 413 | constructor(name: string, private department: string) { 414 | 415 | // If we overwrite the constructor we have to add super() 416 | super(name); 417 | } 418 | } 419 | 420 | const engineer = new Engineer('Peter', 'frontend'); 421 | 422 | console.log(engineer); 423 | // Engineer: { 424 | // "name": "Peter", 425 | // "department": "frontend" 426 | // } 427 | 428 | console.log(engineer.getName); 429 | // "Peter" 430 | 431 | // engineer.setName = ''; 432 | // [ERR]: Name cannot be empty. 433 | 434 | engineer.setName = 'Wendy'; 435 | console.log(engineer.getName); 436 | // "Wendy" 437 | ``` 438 | 439 | ### Abstract classes 440 | 445 | 446 | Important, abstract classes cannot be instantiated directly, if not through a class that extends them. 447 | 448 | If you try this `const paul = new Person('Paul');` TS will complain: `Cannot create an instance of an abstract class.` 449 | This is because abstract classes have missing features, like in our case, the implementation of `logName()` 450 | 451 | ```ts 452 | abstract class Person { 453 | constructor(protected name: string) {} 454 | 455 | abstract logName(): void; 456 | 457 | } 458 | 459 | class Engineer extends Person { 460 | constructor(name: string, private department: string) { 461 | super(name); 462 | } 463 | 464 | logName() { 465 | console.log(this.name); 466 | } 467 | } 468 | 469 | const peter = new Engineer('Peter', 'frontend'); 470 | peter.logName(); 471 | // "Peter" 472 | ``` 473 | 474 | If we don't implement `logName()` on the `Engineer` class, TS will complain: 475 | `Non-abstract class 'Engineer' does not implement inherited abstract member 'logName' from class 'Person'.` 476 | 477 | Note that both, the class and the method are prefixed with the `abstract` keyword. Also, that we are using `protected` for name since we are accessing to it in `Engineer`: 478 | 479 | ```ts 480 | logName() { 481 | console.log(this.name); 482 | } 483 | ``` 484 | 485 | ### Static members 486 | (Members are: properties, constructor and methods) 487 | 488 | 490 | 491 | 496 | 497 | In the following example we are instantiating `2 objects` with the class `User`. 498 | 499 | `Each object` is going to have **ALL** the properties and methods defined in `User` class. 500 | 501 | ```ts 502 | class User { 503 | 504 | message = `Hi!`; 505 | 506 | greet(): string { 507 | return this.message 508 | } 509 | } 510 | 511 | const user1 = new User(); 512 | const user2 = new User(); 513 | 514 | 515 | console.log(user1.greet()); // Hi! 516 | console.log(user2.greet()); // Hi! 517 | ``` 518 | 519 | Snippet to quick check properties and methods: 520 | 521 | ```ts 522 | console.log('message' in user1, 'greet' in user1); // true, true 523 | console.log('message' in user2, 'greet' in user2); // true, true 524 | ``` 525 | 526 | We can change this behavior making the objects use the properties and methods of the class itself with the `static` keyword. 527 | 528 | ```ts 529 | class User { 530 | 531 | static message = `Hi!`; 532 | 533 | static greet(): string { 534 | return this.message 535 | } 536 | } 537 | 538 | const user1 = new User(); 539 | const user2 = new User(); 540 | ``` 541 | 542 | Now, if we check for those properties and methods... 543 | 544 | ```ts 545 | console.log('message' in user1, 'greet' in user1); // false, false 546 | console.log('message' in user2, 'greet' in user2); // false, false 547 | ``` 548 | 549 | Since those members only exist in the class itself (`User`), we can access through the class: 550 | 551 | ```ts 552 | console.log(User.greet()); // Hi! 553 | ``` 554 | 555 | And in this case, we don't need to instantiate. 556 | 557 | Full example: 558 | 559 | ```ts 560 | class User { 561 | 562 | static message = `Hi!`; 563 | 564 | static greet(): string { 565 | return this.message 566 | } 567 | } 568 | 569 | 570 | console.log(User.greet()); // Hi! 571 | ``` 572 | 573 | 574 | ### Private Constructors 575 | 576 | 581 | 582 | ```ts 583 | abstract class Person { 584 | constructor(protected name: string) {} 585 | 586 | abstract logName(): void; 587 | 588 | } 589 | 590 | class Engineer extends Person { 591 | 592 | private static instance: Engineer; 593 | 594 | private constructor(name: string, private department: string) { 595 | super(name); 596 | } 597 | 598 | static getInstance() { 599 | if (Engineer.instance) { 600 | return this.instance; 601 | } 602 | this.instance = new Engineer('Peter', 'frontend'); 603 | return this.instance; 604 | } 605 | 606 | logName() { 607 | console.log(this.name); 608 | } 609 | } 610 | 611 | const peter = Engineer.getInstance(); 612 | console.log(peter); 613 | // Engineer: { 614 | // "name": "Peter", 615 | // "department": "frontend" 616 | // } 617 | 618 | const paul = Engineer.getInstance(); 619 | console.log(paul); 620 | // Engineer: { 621 | // "name": "Peter", 622 | // "department": "frontend" 623 | // } 624 | ``` 625 | 626 | 627 | **Note about Interfaces and Abstract classes** 628 | 629 | I know this sections covers a lot, however, I'd be happy if you can remember at least the following: 630 | 631 | 1. Interfaces are blueprints of objects. 632 | You CANNOT instantiate an Interface since there are no implementations details. 633 | You CAN describe the object. 634 | 635 | 2. Abstract classes 636 | You CANNOT instantiate an abstract class. 637 | You CAN sublclass it (class Dog extends Animal) 638 | It can have or not abstract methods 639 | 640 | 641 | ```ts 642 | interface IAnimal { 643 | eat(): void 644 | } 645 | 646 | abstract class Animal { 647 | eat() { 648 | console.log(`Eating`); 649 | } 650 | } 651 | 652 | 653 | class Cat implements IAnimal { 654 | // we need to implement the method eat() since Interfaces dont support implementation details 655 | 656 | // Compiler error: Class 'Cat' incorrectly implements interface 'IAnimal'. 657 | // Property 'eat' is missing in type 'Cat' but required in type 'IAnimal'. 658 | } 659 | 660 | class Dog extends Animal { 661 | 662 | } 663 | 664 | let bulldog = new Dog(); 665 | bulldog.eat(); 666 | // "Eating" 667 | 668 | let siamese = new Cat(); 669 | siamese.eat(); 670 | // Compiler error: Property 'eat' does not exist on type 'Cat'. 671 | // Runtime error: siamese.eat is not a function 672 | 673 | 674 | // You cannot instantiate an Interface 675 | // You cannot instantiate an abstract class directly. Example: 676 | 677 | let bird = new Animal(); 678 | // Compiler error: Cannot create an instance of an abstract class. 679 | ``` -------------------------------------------------------------------------------- /01_7_functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | --- 4 | 5 | * [Void](#void) 6 | + [Void and return extended](#void-and-return-extended) 7 | * [Function types](#function-types) 8 | * [Function Overloading](#function-overloading) 9 | * [this parameter](#this-parameter) 10 | * [Assertion functions](#assertion-functions) 11 | 12 | --- 13 | 14 | In JS the return value of functions that don't return is `undefined` 15 | 16 | ```js 17 | const logHi = function() { 18 | console.log(`hi`); 19 | } 20 | 21 | const returnOfLogHi = logHi(); 22 | console.log(`Value is ${returnOfLogHi}`); // Value is undefined 23 | ``` 24 | 25 | ## Void 26 | 27 | In TS we use `void` to state that the function is not going to return. 28 | 29 | ```ts 30 | function logResultToConsole(result: number): void { 31 | console.log(`The result is ${result}`); 32 | } 33 | 34 | logResultToConsole(2); 35 | ``` 36 | 37 | Same example but with `arrow function` 38 | 39 | ```ts 40 | const logResultToConsole: (r: number) => void = (result: number) => { 41 | console.log(`The result is ${result}`); 42 | } 43 | 44 | logResultToConsole(2); 45 | ``` 46 | 47 | ### Void and return extended 48 | 49 | As we saw, we use `void` when we don't want to return. If in the previous snippet we add `return 1`... 50 | 51 | ```ts 52 | function logResultToConsole(result: number): void { 53 | console.log(`The result is ${result}`); 54 | return 1; 55 | } 56 | ``` 57 | 58 | ... we are going to receive the following error: 59 | 60 | ``` 61 | Type 'number' is not assignable to type 'void'. 62 | ``` 63 | 64 | However, we could return nothing and it would work. 65 | 66 | ```ts 67 | console.log(`The result is ${result}`); 68 | return; 69 | } 70 | ``` 71 | 72 | Now... What happens if we pass a `callback` to the function, setting its return type to `void` and `return something` inside the callback. 73 | 74 | 77 | 78 | ```ts 79 | function logResultToConsole(result: number, cb: (a: number) => void): void { 80 | console.log(`The result is ${result}`); 81 | const returnFromCB = cb(result + 1000); 82 | console.log(`This is the return from cb ${returnFromCB}`); 83 | } 84 | 85 | logResultToConsole(1, (a) => { 86 | console.log(a); 87 | return a; 88 | }); 89 | ``` 90 | 91 | This will work and TS is not going to show any error. 92 | 93 | ``` 94 | "The result is 1" 95 | 1001 96 | "This is the return from cb 1001" 97 | ``` 98 | 99 | ## Function types 100 | 101 | When we want to create a reference to another function and we want to enforce the arguments and return type: 102 | 103 | ```ts 104 | let log: (a: number) => void; 105 | 106 | function logResultToConsole(result: number): void { 107 | console.log(`The result is ${result}`); 108 | } 109 | 110 | log = logResultToConsole; 111 | log(3); 112 | ``` 113 | 114 | 119 | 120 | We can use `type aliases` and `interfaces` to describe functions (either something that can be invoked or constructed using the new keyword) 121 | 122 | ```ts 123 | interface IMultiplyBy2 { 124 | (n: number): number 125 | } 126 | 127 | type MultiplyBy2 = (n: number) => number; 128 | 129 | const multiply1: IMultiplyBy2 = (n) => n * 2; 130 | 131 | const multiply2: MultiplyBy2 = (n) => n * 2; 132 | ``` 133 | 134 | We can use `call signatures`, so instead of doing this... 135 | 136 | ```ts 137 | type MultiplyBy2 = (n: number) => number; 138 | 139 | // the function declaration 140 | ``` 141 | 142 | ... we can do this: 143 | 144 | ```ts 145 | type MultiplyBy2 = { 146 | (n: number): number; 147 | } 148 | 149 | // the function declaration 150 | // const multiplyBy2: MultiplyBy2 = (n: number) => { 151 | // return n * 2; 152 | // } 153 | 154 | // console.log(multiplyBy2(4)); // 8 155 | ``` 156 | 157 | We can also be less explicit and create a type alias or interface for several use cases: 158 | 159 | ```ts 160 | interface IHandle2NumbersMathOperations { 161 | (a: number, b: number): number 162 | } 163 | 164 | const add: IHandle2NumbersMathOperations = (a, b) => a + b; 165 | const multiply: IHandle2NumbersMathOperations = (a, b) => a * b; 166 | ``` 167 | 168 | As long as our function gets 2 numbers as arguments and returns a number it will comply. 169 | 170 | As we did with functions (call signatures) we can do the same with constructor functions (construct signatures): 171 | 172 | ```ts 173 | type UserConstuctorSignature = new (username: string) => { username: string } 174 | 175 | const User: UserConstuctorSignature = class { 176 | constructor(public username: string) {} 177 | } 178 | 179 | 180 | // Or what is the same (call signature) 181 | // type UserConstuctorSignature = { 182 | // new (username: string) : { username: string } 183 | // } 184 | 185 | // const User: UserConstuctorSignature = class { 186 | // constructor(public username: string) {} 187 | // } 188 | ``` 189 | 190 | ## Function Overloading 191 | We declare multiple signatures for any function before the function's body. 192 | 193 | Let's take the following example: 194 | 195 | ```ts 196 | function doubleInput(v: number | string) { 197 | if (typeof v == 'string') { 198 | return v.repeat(2); 199 | } 200 | else { 201 | return (v * 2); 202 | } 203 | } 204 | 205 | const v1 = doubleInput('1'); 206 | const v2 = doubleInput(1); 207 | ``` 208 | 209 | If we hover over `v1` (or v2) we will see the follwoing: `const v1: string | number` 210 | 211 | We can improve this with function overloading. 212 | 213 | ```ts 214 | function doubleInput(number: number): number; 215 | function doubleInput(string: string): string; 216 | 217 | function doubleInput(v: number | string) { 218 | if (typeof v == 'string') { 219 | return v.repeat(2); 220 | } 221 | else { 222 | return (v * 2); 223 | } 224 | } 225 | 226 | const v1 = doubleInput('1'); 227 | const v2 = doubleInput(1); 228 | ``` 229 | 230 | Now, if we hover over `v1` the compiler is going to show `string` and `number` for `v2`. 231 | 232 | 239 | 240 | ## this parameter 241 | (remember this is the `calling context` in JS) 242 | 243 | `this` as a parameter should be always be the first parameter. 244 | It is only going to be used bt TS during compilation. 245 | 246 | 247 | ```ts 248 | function greet(this: { name: string }) { 249 | return `Hi ${this.name}` 250 | } 251 | 252 | // greet points to the utility function greet() 253 | const user = { 254 | name: 'Peter', 255 | greet 256 | } 257 | 258 | console.log( 259 | user.greet() 260 | ) 261 | // "Hi Peter" 262 | ``` 263 | 264 | So, if we invoke `greet()` on an `object` that has a property `name` it will work, if not, TS will error: 265 | 266 | ```ts 267 | const person = { 268 | name: 'Wendy', 269 | greet 270 | } 271 | 272 | const chair = { 273 | color: 'brown', 274 | greet 275 | } 276 | 277 | person.greet() 278 | 279 | chair.greet() 280 | // The 'this' context of type '{ color: string; greet: (this: { name: string; }) => string; }' is not assignable to method's 'this' of type '{ name: string; }'. 281 | // Property 'name' is missing in type '{ color: string; greet: (this: { name: string; }) => string; }' but required in type '{ name: string; }'. 282 | ``` 283 | 284 | 285 | 289 | 290 | ## Assertion functions 291 | Imagine we are making a network request and we have a function that checks if we receive the proper response or not (in this case, null). 292 | 293 | If the response is an `array of User` we should be able to access the first element and its name property. 294 | However, no matter the case, TS will error with `Object is possibly 'null'.`. 295 | This is because TS doesn't do an implicit assertion checking. 296 | 297 | ```ts 298 | type User = { 299 | name: string 300 | } 301 | 302 | function assertUser(condition: unknown, message: string) { 303 | if (!condition) throw new Error(message); 304 | } 305 | 306 | function getUsers(): (null | User[]) { 307 | const response = [null, [ { name: 'Peter'}, { name: 'Wendy' } ] ]; 308 | return response[Math.floor(Math.random() * ((response.length) - 0) + 0)]; 309 | } 310 | 311 | const users = getUsers(); 312 | 313 | assertUser(users != null, 'Something went wrong!'); 314 | 315 | 316 | console.log(users[0].name); 317 | // Object is possibly 'null'. 318 | ``` 319 | 320 | However, we can use an `assertion function` to do `explicit assertion checking` 321 | For this, we add to our assertion function a return type of `asserts parameter`. Doing that, we are telling the compiler that the function will only return if the condition is `true` (which, in our case would be if users is not null) 322 | 323 | ```ts 324 | type User = { 325 | name: string 326 | } 327 | 328 | function assertUser(condition: unknown, message: string): asserts condition { 329 | if (!condition) throw new Error(message); 330 | } 331 | 332 | function getUsers(): (null | User[]) { 333 | const response = [null, [ { name: 'Peter'}, { name: 'Wendy' } ] ]; 334 | return response[Math.floor(Math.random() * ((response.length) - 0) + 0)]; 335 | } 336 | 337 | const users = getUsers(); 338 | 339 | assertUser(users != null, 'Something went wrong!'); 340 | 341 | 342 | console.log(users[0].name); 343 | 344 | ``` 345 | 346 | -------------------------------------------------------------------------------- /02_0_type-assertions-casting-declaration.md: -------------------------------------------------------------------------------- 1 | # Type Assertions, Type Casting and Type Declaration 2 | 3 | --- 4 | 5 | * [Type Assertions](#type-assertions) 6 | * [Type Casting or Type Coercion](#type-casting-or-type-coercion) 7 | * [Type Declarations](#type-declarations) 8 | 9 | --- 10 | 11 | ## Type Assertions 12 | 13 | 37 | 38 | ## Type Casting or Type Coercion 39 | This is done by JS (not a TS feature) 40 | 41 | ```ts 42 | let value: string | number = '123'; 43 | 44 | value = +value; 45 | 46 | console.log(typeof value); // number 47 | ``` 48 | 49 | ## Type Declarations 50 | 51 | 52 | We don't provide implementation within declarations. 53 | 54 | If we try to access the property `PORT` from the env. variables of our runtime 55 | 56 | ```ts 57 | const user = process.env.PORT; 58 | ``` 59 | 60 | TS will error with: 61 | 62 | ``` 63 | Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`. 64 | ``` 65 | 66 | We can install `@types/node` or declare the type: 67 | 68 | ```ts 69 | declare const process: any; 70 | 71 | const user = process.env.PORT; 72 | ``` 73 | 74 | We can move the declarations to their own file. 75 | The naming convention is: `file-name.d.ts` 76 | Example: `process.d.ts` 77 | 78 | Always prefer "types packages" (example: @types/node) to creating you owns. Yet, it can be useful for libraries that don't have their declarations. 79 | 80 | You can search for types packages here: https://github.com/DefinitelyTyped/DefinitelyTyped 81 | 82 | Note: today, most of the projects support TS so you don't have to install a "type package". -------------------------------------------------------------------------------- /02_1_type-guards.md: -------------------------------------------------------------------------------- 1 | # Type Guards 2 | 3 | 6 | 7 | --- 8 | 9 | * [User defined Type Guards](#user-defined-type-guards) 10 | 11 | --- 12 | 13 | ```ts 14 | let something: unknown 15 | something = {}; 16 | something = '1'; 17 | something = 1; 18 | 19 | 20 | if (typeof something === 'number') console.log('number'); 21 | else console.log('other'); 22 | ``` 23 | 24 | We can check if... 25 | 1. It is an instanceof: `if (something instanceof Person) {}` 26 | 2. Its value is x: `if (something === 3) {}` 27 | 3. Its type is y: `if (typeof something === 'number') {` 28 | 4. It has a value (or hasn't): `if (something) {}` 29 | 5. It has a particular property: `if ('property' in something) {}` 30 | 6. Use built-in functions, for example to check if it is an array: `if (Array.isArray(something)) {}` 31 | 32 | --- 33 | 34 | For objects created using classes we have to check if it is an instance of a particular class. 35 | 36 | ```ts 37 | type Animal = Cat | Dog; 38 | 39 | class Cat { 40 | meow() { 41 | console.log(`meow`); 42 | } 43 | } 44 | class Dog { 45 | bark() { 46 | console.log(`bark`); 47 | } 48 | } 49 | 50 | function animalSound(animal: Animal) { 51 | if (animal instanceof Cat) { 52 | animal.meow(); 53 | } 54 | if (animal instanceof Dog) { 55 | animal.bark(); 56 | } 57 | } 58 | 59 | animalSound(new Cat()); // meow 60 | animalSound(new Dog()); // bark 61 | ``` 62 | 63 | For object literals: 64 | 65 | ```ts 66 | type Animal = Cat | Fish; 67 | 68 | type Cat = { 69 | walk: Function 70 | } 71 | 72 | type Fish = { 73 | swim: Function 74 | } 75 | 76 | const cat: Cat = { 77 | walk: function(): void { 78 | console.log(`walking`); 79 | } 80 | } 81 | 82 | const fish: Fish = { 83 | swim: function(): void { 84 | console.log(`swimming`); 85 | } 86 | } 87 | 88 | function move(animal: Animal) { 89 | if ('walk' in animal) { 90 | animal.walk(); 91 | } 92 | if ('swim' in animal) { 93 | animal.swim(); 94 | } 95 | } 96 | 97 | move(cat); // walking 98 | move(fish); // swimming 99 | ``` 100 | 101 | ## User defined Type Guards 102 | A function that returns a boolean and it is annotated in the form of: `parameter is Type` 103 | 104 | ```ts 105 | type Animal = Cat | Fish; 106 | 107 | type Cat = { 108 | walk: Function 109 | } 110 | 111 | type Fish = { 112 | swim: Function 113 | } 114 | 115 | const cat: Cat = { 116 | walk: function(): void { 117 | console.log(`walking`); 118 | } 119 | } 120 | 121 | const fish: Fish = { 122 | swim: function(): void { 123 | console.log(`swimming`); 124 | } 125 | } 126 | 127 | // User Defined Type Guards 128 | function isCat(animal: Animal): animal is Cat { 129 | return 'walk' in animal; 130 | } 131 | 132 | function isFish(animal: Animal): animal is Fish { 133 | return 'swim' in animal; 134 | } 135 | 136 | 137 | function move(animal: Animal) { 138 | if (isCat(animal)) { 139 | animal.walk(animal); 140 | } 141 | if (isFish(animal)) { 142 | animal.swim(); 143 | } 144 | } 145 | 146 | move(cat); // walking 147 | move(fish); // swimming 148 | ``` -------------------------------------------------------------------------------- /02_2_intersection-types.md: -------------------------------------------------------------------------------- 1 | # Intersection Types 2 | Or how to combine types. 3 | 4 | --- 5 | 6 | * [Type example with objects](#type-example-with-objects) 7 | * [Interface example with objects](#interface-example-with-objects) 8 | 9 | --- 10 | 11 | `Interfaces` only support `objects`, so if you need to combine other types than objects, you will need `types` 12 | 13 | ## Type example with objects 14 | 15 | ```ts 16 | type Person = { 17 | name: string; 18 | } 19 | 20 | type Engineer = { 21 | employer: string; 22 | } 23 | 24 | type User = Person & Engineer; 25 | 26 | const user: User = { 27 | name: 'Peter', 28 | employer: 'Apple' 29 | } 30 | ``` 31 | 32 | 35 | 36 | ## Interface example with objects 37 | 38 | ```ts 39 | interface Person { 40 | name: string; 41 | } 42 | 43 | interface Engineer { 44 | employer: string; 45 | } 46 | 47 | interface User extends Person, Engineer {} 48 | 49 | const user: User = { 50 | name: 'Peter', 51 | employer: 'Apple' 52 | } 53 | ``` -------------------------------------------------------------------------------- /03_0_generics.md: -------------------------------------------------------------------------------- 1 | # Generics 2 | 3 | 7 | 8 | --- 9 | 10 | * [Generic constraints](#generic-constraints) 11 | 12 | --- 13 | 14 | Let's say that we have a `Queue` class that implements a Queue data Structure. 15 | 16 | ```ts 17 | class Queue { 18 | messages: any[] = [] 19 | 20 | push(message: any): void { 21 | this.messages.push(message); 22 | } 23 | 24 | pop() { 25 | return this.messages.shift(); 26 | } 27 | } 28 | 29 | const q = new Queue(); 30 | 31 | q.push('Hello'); 32 | q.push('Hi'); 33 | q.push(1); 34 | q.push('Hola'); 35 | 36 | console.log(q); 37 | // Queue: { 38 | // "messages": [ 39 | // "Hello", 40 | // "Heo", 41 | // 1, 42 | // "Hi" 43 | // ] 44 | // } 45 | 46 | console.log(q.pop().toUpperCase()); // HELLO 47 | console.log(q.pop().toUpperCase()); // HE 48 | console.log(q.pop().toUpperCase()); // Error: q.pop(...).toUpperCase is not a function 49 | ``` 50 | 51 | This `Queue` accepts any message data type. You can pass a string, number, object... 52 | 53 | However, this flexibility can end in `runtime errors`. 54 | 55 | In the previous example we first push a `string` and then a `number`. 56 | Then, we assume the `Queue` is an array of `strings` and decide to uppercase each message. 57 | 58 | Once we reach the element `1` we receive the following error: 59 | 60 | ``` 61 | [ERR]: q.pop(...).toUpperCase is not a function 62 | ``` 63 | 64 | As you know, there is no toUpperCase method for the Number object. 65 | 66 | We could fix this creating a `subclass` for just `strings`, example: `QueueString` 67 | 68 | ```ts 69 | class Queue { 70 | messages: any[] = [] 71 | 72 | push(message: any): void { 73 | this.messages.push(message); 74 | } 75 | 76 | pop() { 77 | return this.messages.shift(); 78 | } 79 | } 80 | 81 | class QueueString extends Queue { 82 | constructor() { 83 | super(); 84 | } 85 | 86 | push(message: string): void { 87 | this.messages.push(message); 88 | } 89 | 90 | pop() { 91 | return this.messages.shift(); 92 | } 93 | } 94 | 95 | const q = new QueueString(); 96 | 97 | q.push('Hello'); 98 | q.push('Hi'); 99 | q.push(1); // Argument of type 'number' is not assignable to parameter of type 'string'. 100 | ``` 101 | 102 | If we try to push anything else but a `string` we will receive the following error: 103 | 104 | ``` 105 | Argument of type 'number' is not assignable to parameter of type 'string'. 106 | ``` 107 | 108 | But what happened if we need to support multiple data types. We'd probably end creating different subclasses for each data type: `QueueNumber`, `QueueObject`... 109 | 110 | To avoid specific classes we can use `Generics` 111 | 112 | We can add a generic type parameter as an argument to the Queue class. 113 | Then, we specify the type of `message` and the return type of `pop()`. 114 | Lastly, we use `!` (non-null assertion operator), to tell TS that the message is always going to be a `string` (in this case) and it **will not** be undefined or null. 115 | 116 | When we initialize a new `Queue`, we pass in the generic type argument. In this case, `string` 117 | 118 | ```ts 119 | class Queue { 120 | messages: Array = [] 121 | 122 | push(message: T): void { 123 | this.messages.push(message); 124 | } 125 | 126 | pop(): T { 127 | return this.messages.shift()!; 128 | } 129 | } 130 | 131 | const q = new Queue(); 132 | 133 | q.push('Hello'); 134 | q.push('Hi'); 135 | q.push(1); // Argument of type 'number' is not assignable to parameter of type 'string'. 136 | ``` 137 | 138 | Now if we try to push a number, we will see the expected error: 139 | 140 | ``` 141 | Argument of type 'number' is not assignable to parameter of type 'string'. 142 | ``` 143 | 144 | ## Generic constraints 145 | 146 | 150 | 151 | Example: `` 152 | 153 | In the following example, we have the function `processOrder()` that... 154 | 1. Takes an `order` (object) of type `T` 155 | 3. The object that takes (`T`) conforms to the type Order (`` without this, we would nolt be able to access the order properties like `order.shopper`) 156 | 2. Returns a `new object` containing: 157 | * The order (object of type T) 158 | * orderId 159 | * name 160 | 161 | ```ts 162 | type Products = { 163 | [k: string]: object 164 | } 165 | 166 | type Order = { 167 | products: Products, 168 | shopper: string 169 | } 170 | 171 | function processOrder(order: T, orderId: number): T & { orderId: number } & { name: string } { 172 | return { 173 | ...order, 174 | orderId, 175 | name: `${order.shopper}` 176 | } 177 | } 178 | 179 | const order = { 180 | products: { 181 | candies: { 182 | quantity: 10 183 | } 184 | }, 185 | shopper: 'Peter Pan' 186 | } 187 | 188 | 189 | const processedOrder = processOrder(order, 1000); 190 | 191 | console.log(processedOrder); 192 | // { 193 | // "products": { 194 | // "candies": { 195 | // "quantity": 10 196 | // } 197 | // }, 198 | // "shopper": "Peter Pan", 199 | // "orderId": 1000, 200 | // "name": "Peter Pan" 201 | // } 202 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript: the typed superset of JS 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) 4 | 5 | --- 6 | 7 | **This is an ongoing work. See this repo, until new advice, as a collection of notes. Thanks!** 8 | 9 | --- 10 | 11 | ## Notes 12 | 13 | As I did years ago with `React` (tutorial that I should resume someday; please, forgive my laziness), I decided to put (and publish) some notes related to the new kid on the block, the JS Superset `TypeScript`. 14 | 15 | Later, we will use `typescript` locally (feel free to install it as a global package: `npm install -g typescript`). But, for the moment, we consume [repl.it](https://repl.it/languages/typescript) 16 | 17 | ## Index 18 | 19 | * [Intro](./00_0_intro.md) 20 | 21 | * [Structural Typing](./00_1_0_structural-typing.md) 22 | 23 | * Type Inference and Type Annotation 24 | * [Type Annotation](./01_1_1_type-inference-and-type-annotation.md#type-annotation-or-type-assignment) 25 | * [Type Inference](./01_1_1_type-inference-and-type-annotation.md#type-inference) 26 | * [Const Assertion](./01_1_1_type-inference-and-type-annotation.md#const-assertion) 27 | 28 | * Types: 29 | * [String](./01_2_types-string-number-boolean-union.md#string) 30 | * [Number](./01_2_types-string-number-boolean-union.md#number) 31 | * [Boolean](./01_2_types-string-number-boolean-union.md#boolean) 32 | * [Union Types (OR)](./01_2_types-string-number-boolean-union.md#union-types) 33 | * [Discriminated Unions](./01_2_types-string-number-boolean-union.md#discriminated-unions) 34 | * [Intersection Types (AND)](./01_2_types-string-number-boolean-union.md#intersection-types) 35 | * [Null](./01_3_types-null-undefined-void-never-any-unknown.md#null) 36 | * [Undefined](./01_3_types-null-undefined-void-never-any-unknown.md#undefined) 37 | * [Void](./01_3_types-null-undefined-void-never-any-unknown.md#void) 38 | * [Never](./01_3_types-null-undefined-void-never-any-unknown.md#never) 39 | * [Any](./01_3_types-null-undefined-void-never-any-unknown.md#any) 40 | * [Unknown](./01_3_types-null-undefined-void-never-any-unknown.md#unknown) 41 | * [Object](./01_4_types-object-array-tuple-enum-set.md#object) 42 | * [Optional modifier (?)](./01_4_types-object-array-tuple-enum-set.md#optional-modifier) 43 | * [Non-null assertion operator (!)](./01_4_types-object-array-tuple-enum-set.md#non-null-assertion-operator) 44 | * [Array](./01_4_types-object-array-tuple-enum-set.md#array) 45 | * [Tuple](./01_4_types-object-array-tuple-enum-set.md#tuple) 46 | * [Enum](./01_4_types-object-array-tuple-enum-set.md#enum) 47 | * [Set](./01_4_types-object-array-tuple-enum-set.md#set) 48 | 49 | * Type Alias and Interfaces: 50 | * [Alias](./01_5_type-alias-interfaces.md#type-alias) 51 | * [Implements](./01_5_type-alias-interfaces.md#implements) 52 | * [Utility types](./01_5_type-alias-interfaces.md#utility-types) 53 | * [Readonly](./01_5_type-alias-interfaces.md#readonly) 54 | * [Required](./01_5_type-alias-interfaces.md#required) 55 | * [Record](./01_5_type-alias-interfaces.md#record) 56 | * [Partial](./01_5_type-alias-interfaces.md#partial) 57 | * [Interfaces](./01_5_type-alias-interfaces.md#interfaces) 58 | * [Extends](./01_5_type-alias-interfaces.md#extends) 59 | * [Implements](./01_5_type-alias-interfaces.md#implements-1) 60 | 61 | * Classes: 62 | * [Classes](./01_6_classes.md#classes) 63 | ... here 64 | 65 | Functions: 66 | * [Functions](./01_7_functions.md#functions) 67 | 68 | Type Assertions, Type Casting and Type Declaration 69 | * [Type Assertions](./02_0_type-assertions-casting-declaration.md#type-assertions) 70 | * [Type Casting](./02_0_type-assertions-casting-declaration.md#type-casting-or-type-coercion) 71 | * [Type Declaration](./02_0_type-assertions-casting-declaration.md#type-declarations) 72 | 73 | Type Guards: 74 | * [Type Guards](./02_1_type-guards.md#type-guards) 75 | 76 | Intersection types: 77 | * [Intersection Types](./02_2_intersection-types.md#intersection-types) 78 | 79 | Generics: 80 | * [Generics](./03_0_generics.md#generics) --------------------------------------------------------------------------------