├── img-screens ├── 1.png ├── 2.png ├── 4.png ├── 5.png ├── 6.png ├── 8.png ├── 9.png ├── 10.png ├── 11.png ├── 12.jpg ├── 13.jpg ├── 3.jpeg └── 7.jpeg ├── public ├── favicon.ico ├── manifest.json └── index.html ├── clases ├── homeworks │ ├── ejercicio 7.ts │ ├── ejercicio 6.ts │ ├── ejercicio 8.ts │ ├── ejercicio 10.ts │ ├── ejercicio 9.ts │ ├── ejercicio 3.ts │ ├── ejercicio 4.ts │ ├── ejercicio 1.ts │ ├── ejercicio 5.ts │ ├── ejercicio 2 │ │ ├── ejercicio 2.ts │ │ └── tsconfig.json │ └── tsconfig.json ├── demos │ ├── arreglos.ts │ ├── duck-typing.ts │ ├── function-overload.ts │ ├── objetos.ts │ ├── clases.ts │ ├── funciones.ts │ ├── generic-functions.ts │ └── video 1.ts └── package.json ├── tsconfig.json ├── .gitignore ├── package.json └── README.md /img-screens/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/1.png -------------------------------------------------------------------------------- /img-screens/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/2.png -------------------------------------------------------------------------------- /img-screens/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/4.png -------------------------------------------------------------------------------- /img-screens/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/5.png -------------------------------------------------------------------------------- /img-screens/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/6.png -------------------------------------------------------------------------------- /img-screens/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/8.png -------------------------------------------------------------------------------- /img-screens/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/9.png -------------------------------------------------------------------------------- /img-screens/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/10.png -------------------------------------------------------------------------------- /img-screens/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/11.png -------------------------------------------------------------------------------- /img-screens/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/12.jpg -------------------------------------------------------------------------------- /img-screens/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/13.jpg -------------------------------------------------------------------------------- /img-screens/3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/3.jpeg -------------------------------------------------------------------------------- /img-screens/7.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/img-screens/7.jpeg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soyHenry/wks-typescript/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 7.ts: -------------------------------------------------------------------------------- 1 | function suma(a: number, b: number): number { 2 | return a + b 3 | } 4 | // Logra que esta funcion tambien concatene strings -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 6.ts: -------------------------------------------------------------------------------- 1 | function suma(a: number, b: number): number { 2 | return a + b 3 | } //que pasa si yo quisiera concatenar strings con esta funcion? -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 8.ts: -------------------------------------------------------------------------------- 1 | //Intenta tipar una pluck function! 2 | //TIP! Buscar la palabra reservada keyof y usar extends! 3 | function pluck(arreglo, prop) { 4 | return arreglo.map((object) => { 5 | return object[prop] 6 | }) 7 | } -------------------------------------------------------------------------------- /clases/demos/arreglos.ts: -------------------------------------------------------------------------------- 1 | let tupla: [string, number] = ['Mati', 29] 2 | 3 | tupla = ['Franco', 27] 4 | 5 | let arreglo : number[] = [1, 2, 3, 4] 6 | 7 | let arregloStrings : string[] = ['mati', 'lamela'] 8 | 9 | let mati = arregloStrings.shift() 10 | 11 | let arregloNumeros: Array = [2, 3, 4, 5, 1] -------------------------------------------------------------------------------- /clases/demos/duck-typing.ts: -------------------------------------------------------------------------------- 1 | interface Instructor { 2 | name: string; 3 | age: number; 4 | } 5 | 6 | function infoInstructor(inst: Instructor) { 7 | console.log(`${inst.name} - ${inst.age} years old`); 8 | } 9 | 10 | const noTypeVar = {name: "Franco", age: 26}; 11 | 12 | infoInstructor(noTypeVar); // "Franco - 26 years old" -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 10.ts: -------------------------------------------------------------------------------- 1 | interface Instructor { 2 | name: string; 3 | age: number; 4 | } 5 | 6 | function infoInstructor(inst: Instructor) { 7 | console.log(`${inst.name} - ${inst.age} years old`); 8 | } 9 | 10 | const noTypeVar = {name: "Franco", age: 26}; 11 | 12 | infoInstructor(noTypeVar); // "Franco - 26 years old" -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 9.ts: -------------------------------------------------------------------------------- 1 | function suma(a: number, b: string): string; 2 | function suma(a: string, b: string): string; 3 | function suma(a: string, b: number): string; 4 | function suma(a: number, b: number): number { 5 | return a + b; 6 | } 7 | 8 | let resultado = suma(2, 2) //cambien los parametros para ver su comportamiento 9 | //haciendo hover sobre resultado 10 | 11 | //Aplicar lo mismo para el resto de las operaciones matematicas! -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 3.ts: -------------------------------------------------------------------------------- 1 | // Array 2 | let tennisPoints: number[] = [15, 30, 40]; 3 | let anotherWay: Array = [15, 30, 40]; 4 | 5 | // Array + Any 6 | let dynamicList: any[] = ["Franco", 26, true]; 7 | 8 | // Tuple 9 | let person: [string, number] = ["Franco", 26]; 10 | //let extendIncorrectPerson: [string, number] = ["Franco", 26, true]; // Error 11 | 12 | //crea una tupla, donde en la primera posicion haya un booleano y en la segunda un string 13 | 14 | //crea un arreglo de strings 15 | 16 | export {} -------------------------------------------------------------------------------- /clases/demos/function-overload.ts: -------------------------------------------------------------------------------- 1 | function makeDate(timestamp: number): Date; 2 | function makeDate(m: number, d: number, y: number): Date; 3 | function makeDate(mOrTimestamp: number, d?: number, y?: number): Date { 4 | if (d !== undefined && y !== undefined) { 5 | return new Date(y, mOrTimestamp, d); 6 | } else { 7 | return new Date(mOrTimestamp); 8 | } 9 | } 10 | 11 | makeDate(12345678); // 1970-01-01T03:25:45.678Z 12 | makeDate(3, 5, 2021); // 2021-04-05T03:00:00.000Z 13 | makeDate(1, 3); // Error: No overload expects 2 arguments, but overloads 14 | // do exist that expect either 1 or 3 arguments. -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /clases/demos/objetos.ts: -------------------------------------------------------------------------------- 1 | interface Persona { 2 | nombre: string; 3 | apellido: string; 4 | edad: number; 5 | hobbies: Hobby[] 6 | } 7 | interface Hobby { 8 | nombre: string 9 | } 10 | 11 | interface Estudiante extends Persona { 12 | // nombre: string; 13 | // apellido: string; 14 | // edad: number; 15 | estaActivo?: boolean; 16 | saluda: (a: string) => void 17 | } 18 | 19 | let matias: Persona = { 20 | nombre: 'matias', 21 | apellido: 'lamela', 22 | edad: 29, 23 | hobbies: [{nombre: 'leer'}] 24 | } 25 | 26 | matias.hobbies[0].nombre 27 | 28 | let franco: Estudiante = { 29 | nombre: 'franco', 30 | apellido: 'etcheverri', 31 | edad: 27, 32 | 33 | saluda: (a: string) => {console.log('hola')}, 34 | hobbies: [] 35 | } 36 | franco.estaActivo //undefined o booleano -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # package-lock.json 26 | package-lock.json 27 | 28 | # eslintcache 29 | .eslintcache 30 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 31 | 32 | # dependencies 33 | /node_modules 34 | /.pnp 35 | .pnp.js 36 | 37 | # testing 38 | /coverage 39 | 40 | # production 41 | /build 42 | 43 | # misc 44 | .DS_Store 45 | .env.local 46 | .env.development.local 47 | .env.test.local 48 | .env.production.local 49 | 50 | npm-debug.log* 51 | yarn-debug.log* 52 | yarn-error.log* 53 | -------------------------------------------------------------------------------- /clases/demos/clases.ts: -------------------------------------------------------------------------------- 1 | class Persona { 2 | nombre: string; //por defecto, son publicas 3 | private edad: number; 4 | protected email: string 5 | constructor(nombre: string, edad: number, email: string) { 6 | this.nombre = nombre; 7 | this.edad = edad 8 | this.email = email 9 | } 10 | getEdad () { 11 | return 'mi edad es ' + this.edad 12 | } 13 | } 14 | 15 | class Estudiante extends Persona { 16 | isActive: boolean 17 | constructor(nombre: string, edad: number, email: string) { 18 | super(nombre, edad, email) 19 | this.isActive = false 20 | } 21 | funcionPrueba() { 22 | this.email 23 | } 24 | } 25 | 26 | 27 | let mati = new Persona('matias', 29, 'mati@mail.com') 28 | let fede = new Persona('fede', 29, 'fede@mail.com') 29 | fede.email 30 | mati.nombre //publico, lo puedo acceder desde fuera de la clase 31 | mati.getEdad() 32 | export {} -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 4.ts: -------------------------------------------------------------------------------- 1 | //tipado inferido 2 | let mati = { 3 | nombre: 'Matias', 4 | edad: 29 5 | } 6 | 7 | //tipado explicito? 8 | 9 | interface Persona { 10 | nombre: string; 11 | edad: number 12 | } 13 | 14 | let franco: Persona = { 15 | nombre: "Franco", 16 | edad: 27 17 | } 18 | 19 | interface Estudiante extends Persona { 20 | esActivo: boolean 21 | } 22 | 23 | let diego: Estudiante = { 24 | nombre: "Diego", 25 | edad: 29, 26 | esActivo: false //que pasa si comento alguno? 27 | } 28 | 29 | // Investiga la palabra reservada implements 30 | 31 | // Existe otra alternativa para realizar lo mismo? 32 | // Si, types. 33 | 34 | 35 | type Person = { 36 | name: string, 37 | age: number 38 | } 39 | 40 | type Student = Person & { 41 | isActive: boolean; 42 | } 43 | 44 | let Fede: Student = { 45 | name: 'Fede', 46 | age: 29, 47 | isActive: false 48 | } 49 | 50 | //Investiga sus diferencias 51 | 52 | export {} -------------------------------------------------------------------------------- /clases/demos/funciones.ts: -------------------------------------------------------------------------------- 1 | 2 | //function nombreMiFuncion(parametroUno: TIPADO_PARAMETRO_UNO, ...): tipadoReturn 3 | // function suma(a: number, b: number): number { 4 | // return a + b 5 | // } 6 | 7 | // function suma(a: number | string, b: number | string): number | string | void { 8 | // if(typeof a === "number" && typeof b === "number") return a + b 9 | // if(typeof a === "string" && typeof b === "string") return a + b 10 | // } 11 | 12 | function suma(a: string | number, b: string | number): number { 13 | if(typeof a === "string") { 14 | a = parseInt(a) 15 | } 16 | if(typeof b === "string") { 17 | b = parseInt(b) 18 | } 19 | return a + b 20 | } 21 | 22 | function consologea(): void { 23 | console.log('algo') 24 | //react cuando seteemos un estado! 25 | } 26 | 27 | 28 | function throwError(msg: string): never { 29 | throw new Error(msg); 30 | console.log('un valor') 31 | } 32 | 33 | let resultado = suma(2, 2) 34 | -------------------------------------------------------------------------------- /clases/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wks-typescript", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "ejercicio-uno": "nodemon \"./homeworks/ejercicio 1.ts\"", 7 | "ejercicio-dos": "nodemon \"./homeworks/ejercicio 2/ejercicio 2.ts\"", 8 | "ejercicio-tres": "nodemon \"./homeworks/ejercicio 3.ts\"", 9 | "ejercicio-cuatro": "nodemon \"./homeworks/ejercicio 4.ts\"", 10 | "ejercicio-cinco": "nodemon \"./homeworks/ejercicio 5.ts\"", 11 | "ejercicio-seis": "nodemon \"./homeworks/ejercicio 6.ts\"", 12 | "ejercicio-siete": "nodemon \"./homeworks/ejercicio 7.ts\"", 13 | "ejercicio-ocho": "nodemon \"./homeworks/ejercicio 8.ts\"", 14 | "ejercicio-nueve": "nodemon \"./homeworks/ejercicio 9.ts\"", 15 | "ejercicio-diez": "nodemon \"./homeworks/ejercicio 10.ts\"" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "dependencies": { 20 | "nodemon": "^2.0.14", 21 | "ts-node": "^10.3.0", 22 | "typescript": "^4.4.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /clases/demos/generic-functions.ts: -------------------------------------------------------------------------------- 1 | let array = [1, 2, 3, 4] 2 | let arrayStrings = ['a', 'b', 'c'] 3 | 4 | // function firstElement(arr: any[]) { 5 | // return arr[0]; 6 | // } 7 | 8 | 9 | // function firstElement(arr: Type[]): Type { // T, V U 10 | // return arr[0]; 11 | // } 12 | // let elemento = firstElement(array) //implicito 13 | // let elementoString = firstElement(arrayStrings) //explicito 14 | 15 | function merge(obj1: U, obj2: V) { 16 | return { 17 | ...obj1, 18 | ...obj2 19 | }; 20 | } 21 | 22 | /* 23 | actions en redux, las acciones son funciones genericas! 24 | axios.post 25 | axios.get tambien son funciones genericas 26 | response.data ---> informacion de data es T 27 | axios.get ---> response.data --> va a estar persona, y nos va 28 | a dar el tipado 29 | useState de react, TAMBIEN utiliza generic functions! 30 | */ 31 | 32 | merge({name: "Franco"}, {age: 25}); 33 | merge({name: "Franco"}, 25); // no me advierte -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 1.ts: -------------------------------------------------------------------------------- 1 | //Typescript va a inferir nuestra variable en la primer asignacion 2 | let variableInferida = 2; 3 | 4 | //O podemos definir explicitamente el tipado en el momento de su declaracion 5 | let variableExplicita: number = 2; 6 | 7 | //si hacemos hover sobre resultado, vemos que visual studio code infiere que "resultado" 8 | //es un number, porque la suma de dos numeros es SIEMPRE un numero. 9 | let resultado = variableExplicita + variableInferida 10 | 11 | //completar el tipado correspondiente para cada variable 12 | 13 | let unString: null = 'mati'; 14 | 15 | let unNumber: null = 2; 16 | 17 | let unBoolean: null = true; 18 | 19 | let unNull: undefined = null; //que pasa aca? 20 | 21 | let unUndefined: null = undefined //y aca? 22 | 23 | 24 | // Enum 25 | enum Fases { 26 | Primera, //0 27 | Segunda, //1 28 | Tercera //2 29 | } 30 | let phase: Fases = Fases.Primera; // 0 31 | 32 | enum userActions { 33 | fetchUser = "FETCH_USER", // "FETCH_USER" 34 | postUser = "POST_USER" // "POST_USER" 35 | } 36 | 37 | 38 | export {} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wks-typescript", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.8.1", 9 | "@types/jest": "^26.0.20", 10 | "@types/node": "^12.20.4", 11 | "@types/react": "^17.0.2", 12 | "@types/react-dom": "^17.0.1", 13 | "react": "^17.0.1", 14 | "react-dom": "^17.0.1", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.2.2", 17 | "web-vitals": "^1.1.0" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 5.ts: -------------------------------------------------------------------------------- 1 | class Person { 2 | protected name: string; 3 | protected age: number; 4 | //private mail: string; 5 | constructor(name: string, age: number) { 6 | this.name = name; 7 | this.age = age; 8 | } 9 | } 10 | 11 | class Student extends Person { 12 | private regular: boolean; 13 | 14 | constructor(name: string, age: number, regular: boolean) { 15 | super(name, age); 16 | this.regular = regular; 17 | } 18 | 19 | getFullInfo() { 20 | return `${this.name} (${this.age} years old) - ${this.regular ? 'regular' : 'not regular'}`; 21 | } 22 | } 23 | 24 | const student = new Student("Franco", 26, true); 25 | 26 | student.getFullInfo(); // "Franco (26 years old) - not regular" 27 | student.name; // Property 'name' is protected and only accessible within class 'Person' and its subclasses 28 | student.age; // Property 'age' is protected and only accessible within class 'Person' and its subclasses. 29 | student.regular; // Property 'regular' is private and only accessible within class 'Student' 30 | 31 | //proba como cambiar los modificadores de atributos de la clase! 32 | export {} -------------------------------------------------------------------------------- /clases/demos/video 1.ts: -------------------------------------------------------------------------------- 1 | //const/let/var nombreMiVariable : miTipado = asignacion 2 | 3 | let nombre = 'matias' //infiere mi tipado. 4 | 5 | let otroNombre: string = 'matias' //explicito, donde le vamos a decryption 6 | //que tipado queremos. 7 | 8 | let edad: number = 29 9 | 10 | let flotante: number = 2.5 11 | 12 | let verdadero: boolean = true 13 | 14 | verdadero = false 15 | 16 | enum Fases { 17 | Primero, //0 18 | CualquierValor, //1 19 | Tercero //2 20 | } 21 | 22 | Fases.CualquierValor // 1 23 | 24 | enum userActions { 25 | fetchUser = "FETCH_USER", //FETCH_USER 26 | postUser = "POST_USER", //POST_USER 27 | } 28 | 29 | userActions.fetchUser // comparar en un switch en el reducer de redux 30 | 31 | verdadero = 2 32 | 33 | nombre = 29 //error antes de la compilacion 34 | 35 | // cannot read property map of undefined 36 | let objeto = { 37 | nombre: 'matias', 38 | apellido: 'lamela' 39 | } 40 | //funcion asincronica, 41 | //pidiendo a mi rest API mis productos, 42 | // [] ---> undefined o en el momento que tengo mis productos que es un arreglo 43 | 44 | // (e) => {e.target.value} --> e: Event, evento mouseClick, keyDown 45 | 46 | // Request 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 2/ejercicio 2.ts: -------------------------------------------------------------------------------- 1 | // Null 2 | let nullExample: null = null; // Solo puede tomar valor "null" 3 | let numberExample: number = null; // Pero el valor "null" podemos asignarlo 4 | // a cualquier variable 5 | 6 | // Undefined 7 | let undefinedExample: undefined = undefined; // Solo puede tomar valor "undefined" 8 | let stringExample: string = undefined // Pero el valor "undefined" podemos asignarlo 9 | // a cualquier variable 10 | 11 | //proba cambiando las opciones del tsconfig.json 12 | // "strictNullChecks": true 13 | 14 | //Visual studio code siempre va a tomar el tsconfig de la carpeta donde tengas abierto 15 | //tu proyecto, entonces, tal vez no veas los cambios 16 | 17 | function noImplicitType(firstArg, secondArg) { 18 | console.log("First Argument: ", firstArg); 19 | console.log("Second Argument: ", secondArg); 20 | } 21 | 22 | noImplicitType(1,2); 23 | noImplicitType("Franco", "Etcheverri"); 24 | noImplicitType(true, null); 25 | 26 | // Any 27 | let ejemploAny: any = "Matias"; 28 | ejemploAny = 29; 29 | ejemploAny = null; 30 | 31 | // Unknown 32 | let idk: unknown = "Matias"; 33 | idk = 29; 34 | idk = null; 35 | 36 | //en un principio parecen iguales 37 | 38 | let anyValue: any = "Matias"; 39 | let unkValue: unknown = "Matias"; 40 | 41 | let str1: string = anyValue; // Todo OK, puedo asignarlo 42 | // let str2: string = unkValue; // Error: Type 'unknown' is not assignable to type 'string'. 43 | 44 | let str3: string = unkValue as string; // Explicit cast 45 | 46 | anyValue.metodo(); // Todo OK, compila. Falla en tiempo de ejecución si el método no existe 47 | 48 | // unkValue.method(); // Error: no lo permite 49 | 50 | let iDontKnow: unknown = "typescript"; 51 | 52 | //ejercicio 53 | let nowIKnow: string = iDontKnow; //utiliza el casteo explicito para asignarle a nowIKnow un string; 54 | 55 | export {} -------------------------------------------------------------------------------- /clases/homeworks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "commonjs", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | 68 | /* Interop Constraints */ 69 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 70 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 71 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 72 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 73 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 74 | 75 | /* Type Checking */ 76 | "strict": true, /* Enable all strict type-checking options. */ 77 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 78 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 79 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 80 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 81 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 82 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 83 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 84 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 85 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 86 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 87 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 88 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 89 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 90 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 91 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 92 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 93 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 94 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 95 | 96 | /* Completeness */ 97 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 98 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /clases/homeworks/ejercicio 2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "commonjs", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | 68 | /* Interop Constraints */ 69 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 70 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 71 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 72 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 73 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 74 | 75 | /* Type Checking */ 76 | //"strict": true, /* Enable all strict type-checking options. */ 77 | //"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 78 | //"strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 79 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 80 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 81 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 82 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 83 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 84 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 85 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 86 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 87 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 88 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 89 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 90 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 91 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 92 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 93 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 94 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 95 | 96 | /* Completeness */ 97 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 98 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Henry Workshop - Typescript 2 | 3 | ## Overview 4 | 5 | En este workshop vamos a crear una aplicación utilizando React, Redux, express y Typescript. 6 | Primero comenzamos por el front-end, usaremos el script de Create React App para generar un boilerplate inicial y sobre el modificar lo necesario para elaborar la aplicación que se describirá a continuación. 7 | 8 | ## Setup Inicial 9 | 10 | Para crear la estructura de proyecto se ejecuto el comando 11 | 12 | ```bash 13 | npx create-react-app wks-typescript --template typescript 14 | ``` 15 | 16 | Si quieren iniciarlo desde cero pueden volver a hacerlo o directamente forkear este repositorio y trabajar a partir de eso. De elegir esta segunda opción no olviden de realizar el `npm install` para instalar las dependencias necesarias para correr el proyecto. 17 | 18 | Para simplificar un poco la estrucutra que genera el script de React se eliminaron los archivos innecesarios por lo que para comenzar van a tener que crear la carpeta `src` y allí ir colocando los archivos necesarios. 19 | 20 | ## WARM UP 21 | 22 | ### Componentes 23 | 24 | Una vez creada la carpeta `src` vamos a crear nustro componente principal como ya sabíamos en React, sin nada nuevo por lo que simplemente crearemos el archivo `index.tsx` (extensión tsx en vez de jsx al ser Typescript) dentro de la carpeta `src` con el siguiente código: 25 | 26 | ```javascript 27 | import React from 'react'; 28 | import ReactDOM from 'react-dom'; 29 | 30 | function App() { 31 | return
Henry Workshop - Typescript
; 32 | } 33 | 34 | ReactDOM.render(, document.querySelector('#root')); 35 | ``` 36 | 37 | Si ejecutamos `npm start` ya tendremos nuestra aplicación corriendo de forma local. 38 | 39 | #### Props 40 | 41 | Intentemos ahora pasarle como hacíamos en React una prop a ese componente por ejemplo una llamada title que contenga la palabra "Typescript". 42 | 43 | ```javascript 44 | function App({title}) { 45 | return
Henry Workshop - {title}
; 46 | } 47 | 48 | ReactDOM.render(, document.querySelector('#root')); 49 | ``` 50 | 51 | Si vamos a nuestra aplicación veremos lo siguiente: 52 | 53 | ![screen1](./img-screens/1.png) 54 | 55 | Como ya vimos durante la parte teórica, en Typescript necesitamos informar los tipos de datos que vamos a utilizat, en este caso de las propiedades que va a recibir cada componente, para ello utilizaremos una `interface`. Adicionalmente es una buena práctica si utilizamos componentes funcionales determinar el tipo de dato que va a retornar dicha función y no dejarlo libre para que Typescript haga la inferencia de datos. En este caso lo que estamos retornando es un JSX por lo que agregamos `JSX.Element`. 56 | 57 | _NOTA: Recuerden que este error lo está arrojando porque en el archivo tsconfig.json tenemos seteado el flag strict en true por lo que no va a permitir inferencias al tipo any en ninguna parte de nuestra aplicación pero si lo modificamos a false, va a compilar correctamente_ 58 | 59 | ```javascript 60 | interface AppProps { 61 | title: string; 62 | } 63 | 64 | function App({title}:AppProps): JSX.Element { // Sin el destructuring sería App(props:AppProps) 65 | ... 66 | } 67 | 68 | // O con otra sintaxis 69 | const App = ({title}: AppProps): JSX.Element => { 70 | ... 71 | } 72 | ``` 73 | 74 | ¿Qué ocurriría si existen propiedades opcionales que pueden pasarse o no? Más adelante veremos como solucionar este problema pero si prueban verán que tendremos un problema al momento de compilar. Si sacamos la prop title del llamado al componente tendremos el siguiente error: `Property 'title' is missing in type '{}' but required in type 'Readonly'.` 75 | 76 | #### State (Class vs Hooks) 77 | 78 | Si utilizamos `Hooks` el código va a quedar más sencillo ya que no vamos a tener que preocuparnos por definir la estructura de tipos que debe seguir el estado del componente, simplemente utilizamos el hook de `useState` al igual a como ya estábamos acostumbrados. 79 | 80 | ```javascript 81 | function App({title}: AppProps) { 82 | const [counter, setCounter] = useState(0); 83 | 84 | return ( 85 |
86 |

Henry Workshop - {title}

87 |
88 | 89 | 90 |

91 | {counter} 92 |
93 | ); 94 | } 95 | ``` 96 | 97 | Por el contrario si hacemos el mismo componente pero utilizando `Class` como ya sabíamos de React nos quedaría algo así: 98 | 99 | ```javascript 100 | class App extends React.Component { 101 | constructor(props: AppProps) { 102 | super(props); 103 | this.state = {counter: 0}; 104 | } 105 | 106 | onIncrement = (): void => { 107 | this.setState({counter: this.state.counter + 1}); 108 | }; 109 | 110 | onDecrement = (): void => { 111 | this.setState({counter: this.state.counter - 1}); 112 | }; 113 | 114 | render() { 115 | return ( 116 |
117 |

Henry Workshop - {this.props.title}

118 |
119 | 120 | 121 |

122 | {this.state.counter} 123 |
124 | ); 125 | } 126 | } 127 | ``` 128 | 129 | Si analizamos el código es totalmente válido para una aplicación de React, pero al parecer Typescript no estaría muy de acuerdo con eso, ya que al intentar levantar la aplicación nos arrojará este error: 130 | 131 | ![screen2](/img-screens/2.png) 132 | 133 | Lo que nos está faltando para evitar este error es declarar la estructura de tipos del estado del componente de la siguiente forma: 134 | 135 | ```javascript 136 | interface AppState { 137 | counter: number; 138 | } 139 | 140 | class App extends React.Component { 141 | constructor(props: AppProps) { 142 | super(props); 143 | this.state = {counter: 0}; 144 | } 145 | 146 | ... 147 | } 148 | ``` 149 | 150 | Existe otra posibilidad para simplificar la sintaxis del código en el caso de utilizar `Class` que es la siguiente. Sin definir la interfaz del state podemos sobreescribir state dentro del componente: 151 | 152 | ```javascript 153 | class App extends React.Component { 154 | state = {counter: 0}; 155 | 156 | ... 157 | } 158 | ``` 159 | 160 | ## Enunciado 161 | 162 | Lo anterior fue simplemente para practicar el armado de un componente ya sea funcional o de clase pero ahora lo que vamos a intentar hacer es una aplicación utilizando también Redux que consuma una API que vamos a crear con express y muestre los resultados obtenidos. Por lo tanto los componentes que habíamos hecho hasta recién lamentablemente no van a servirnos más por lo que pueden ya eliminarlos y comenzar nuevamente de cero. 163 | 164 | ![screen3](./img-screens/3.jpeg) 165 | 166 | ## Back-end 167 | 168 | ### Dependecias 169 | 170 | Primero creamos una carpeta que se llame api, y en la consola usamos el siguiente ocomando: 171 | 172 | ```bash 173 | npx tsc --init 174 | ``` 175 | 176 | ¿Qué realiza ese comando? Nos genra un archivo llamado tsconfig.json, en el cual vamos a configurar nuestro compilador. Dentro nos vamos a encontrar diferentes, las mas importantes que tenemos que revisar son: 177 | 178 | ```javascript 179 | "target": "es6", 180 | "module": "commonjs", 181 | "outDir": "./dist", 182 | ``` 183 | 184 | Esas son las configuraciones que va a realizar el compilador para convertir nuestro codigo typescript a javascript para que node lo pueda entender. Target es el standard, commonjs es el modo como van a generarse los modulos y outdir es la carpeta donde se va a guardar nuestro codigo typescript. 185 | Para poder utilizar la libreria de sequelize, es necesario que habilitemos los decoradores y utilicemos ES6, por lo que dejamos target en "es6" y activamos las siguientes opciones: 186 | 187 | ```javascript 188 | "experimentalDecorators": true, 189 | "emitDecoratorMetadata": true, 190 | ``` 191 | 192 | Ahora procedemos a generar nuestro package.json, utilizando el comando: 193 | 194 | ```bash 195 | npm init 196 | ``` 197 | 198 | Le colocamos el nombre que deseemos, y comenzamos a instalar dependencias: 199 | 200 | Instalamos lo que usariamos normalmente en una aplicación de express 201 | 202 | ```bash 203 | npm i cookie-parser cors dotenv express pg sequelize sequelize-typescript typescript 204 | ``` 205 | 206 | Y realizamos otra instalación, noten acá que tambien estamos instalando dependencias con @types ¿Qué son estos @types? 207 | Asi como nosotros tenemos que agregarle los tipos a todos nuestros objetos y funciones, las librerias tambien, entonces necesitamos instalarlas. 208 | Además estamos instalando ts-node, que es el que nos va a permitir utilizar nodemon con typescript! 209 | 210 | ```bash 211 | npm i @types/cookie-parser @types/cors @types/express @types/morgan @types/node @types/pg morgan nodemon ts-node --save-dev 212 | ``` 213 | 214 | Ahora modificamos nuestro script, agregamos el comando 215 | 216 | ```javascript 217 | "scripts": { 218 | "dev": "nodemon index.ts" 219 | }, 220 | ``` 221 | 222 | Ahora creemos nuestro index.ts en nuestra carpeta api y ya podemos comenzar! 223 | 224 | Ese va a ser nuestro punto de entrada, basicamente, es donde vamos a usar nuestro app.listen y sequelize.sync, antes de eso, creemos una carpeta que se llame src, y ahi dentro creemos nuestro app.ts y comencemos a configurar nuestra aplicacion de express! 225 | 226 | ```javascript 227 | import express from 'express'; 228 | const app = express(); 229 | export default app; 230 | ``` 231 | 232 | Primero podemos notar dos cosas, uno, podemos usar es6 dentro de node.js, y que, dependiendo de nuestra configuracion, app probablemente nos este dando un error ¿Por qué? Porque le faltan los tipos! Entonces: 233 | 234 | ```javascript 235 | import express, {Application} from 'express'; 236 | const app: Application = express(); 237 | export default app; 238 | ``` 239 | 240 | Lo que estamos haciendo ahi, es decirle nuestra variable app que es una Application de express. Ahora, seguro estas pensando ¿Como hago yo para saber eso? Simple! Esta en la documentacion. Casi todos los paquetes de npm tienen soporte para typescript, algunos hasta estan escritos en TS! 241 | 242 | Antes de continuar, seteemos nuestras variables de entorno, para eso, hagamos un archivo de configuracion. Creemos una carpeta llamada lib, y dentro de ella un archivo que se llame config.ts 243 | 244 | Adentro de config.ts, vamos a obtener nuestros valores de las variables de entorno: 245 | 246 | ```javascript 247 | import dotenv from 'dotenv'; 248 | 249 | dotenv.config(); 250 | 251 | const config = { 252 | dbUser: process.env.DB_USER || 'postgres', 253 | dbPassword: process.env.DB_PASSWORD || '1234', 254 | dbHost: process.env.DB_HOST || 'localhost', 255 | dbName: process.env.DB_NAME || 'workshop', 256 | dbPort: process.env.DB_PORT || '5432', 257 | dev: process.env.NODE_ENV !== 'production', 258 | port: process.env.API_PORT || '3001', 259 | host: process.env.API_host || 'localhost', 260 | cors: process.env.CORS || 'localhost:3000', 261 | }; 262 | 263 | export default config; 264 | ``` 265 | 266 | Para esto, tambien deberiamos crear nuestro archivo .env, de la siguiente manera: 267 | 268 | ```bash 269 | DB_USER="postgres" 270 | DB_PASSWORD="1234" 271 | DB_HOST="localhost" 272 | DB_NAME="workshop" 273 | DB_PORT="5432" 274 | NODE_ENV="development" 275 | API_PORT="3001" 276 | API_HOST="localhost" 277 | CORS="http://localhost:3000" 278 | ``` 279 | 280 | Ahora seteemos nuestros headers y cors, 281 | 282 | ```javascript 283 | import express, {Application} from 'express'; 284 | import config from './lib/config'; 285 | 286 | const app: Application = express(); 287 | app.use(express.urlencoded({extended: true, limit: '50mb'})); //middleware 288 | app.use(express.json({limit: '50mb'})); 289 | app.use(cookieParser()); 290 | app.use(morgan('dev')); 291 | 292 | app.use( 293 | cors({ 294 | origin: config.cors, 295 | credentials: true, 296 | methods: ['GET', 'POST', 'OPTIONS', 'PUT', 'DELETE'], 297 | allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept'], 298 | }) 299 | ); 300 | 301 | app.use((err, req, res, next) => { 302 | // eslint-disable-line no-unused-vars 303 | const status = err.status || 500; 304 | const message = err.message || err; 305 | console.error(err); 306 | res.status(status).send(message); 307 | }); 308 | 309 | export default app; 310 | ``` 311 | 312 | Nuestro middleware de control de errores nos esta dando problemas, necesita que le especifiquemos su tipado! 313 | Entonces, tenemos que obtener de express NextFunction, Request y Response, pero ¿Y err? 314 | Bueno, no existe un tipado para los errores, existen varias formas de resolver ese problema. Principalmente porque los errores se declaran de distinta forma dependiendo de su origen. 315 | La forma mas sencilla es que hagamos una interface! 316 | 317 | ```javascript 318 | import express, {Request, Response, NextFunction} from 'express'; 319 | 320 | //----------------------------------- 321 | 322 | interface error { 323 | status: number; 324 | message: string; 325 | } 326 | app.use((err: error, req: Request, res: Response, next: NextFunction) => { 327 | // eslint-disable-line no-unused-vars 328 | const status = err.status || 500; 329 | const message = err.message || err; 330 | console.error(err); 331 | res.status(status).send(message); 332 | }); 333 | ``` 334 | 335 | Genial, ahora lo que tenemos que hacer es probar nuestra configuracion, para eso generamos una ruta! 336 | 337 | ```javascript 338 | app.get('/', (req: Request, res: Response) => { 339 | res.send('hola typescript!'); 340 | }); 341 | ``` 342 | 343 | Y en nuestro archivo index.ts, el que esta en la carpeta raiz: 344 | 345 | ```javascript 346 | import app from './src/app'; 347 | 348 | app.listen(3001, function () { 349 | console.log('App is listening on port 3001!'); 350 | }); 351 | ``` 352 | 353 | Como pueden ver, la unica diferencia con express normal es que tenemos que agregar ciertos tipados! Probamos que funcione, y debuggeamos nuestro código. 354 | 355 | Una vez que funcione, creamos nuestra carpeta routes! 356 | Todavía no te conte que tipo de app vamos a hacer, la idea es hacer modelo de usuario con nombre y apellido. 357 | Entonces, en la carpeta routes creamos nuestro index.ts y user.ts 358 | En user.ts generamos nuestras rutas! 359 | 360 | ```javascript 361 | import {Response, Request, Router} from 'express'; 362 | 363 | router.get('/', (req: Request, res: Response) => { 364 | res.send('soy la ruta get!'); 365 | }); 366 | 367 | router.post('/', (req: Request, res: Response) => { 368 | res.send('soy la ruta post!'); 369 | }); 370 | 371 | export default router; 372 | ``` 373 | 374 | Y en nuestro index.ts dentro de la carpeta routes: 375 | 376 | ```javascript 377 | import {Router} from 'express'; 378 | import userRoutes from './user'; 379 | const router = Router(); 380 | 381 | router.use('/user', userRoutes); 382 | 383 | export default router; 384 | ``` 385 | 386 | Ahora volvemos a nuestro app.ts, y agregamos nuestras rutas! 387 | 388 | ```javascript 389 | import routes from './routes/index'; 390 | //----------------------------------- 391 | app.use('/api', routes); 392 | ``` 393 | 394 | Una vez tenemos nuestras rutas, lo que vamos a hacer es crear nuestros modelos! 395 | Para esta guia, solo vamos usar nombre y apellido, pero si ya quieren ir armando el modelo de usuario de su proyecto, pueden hacerlo. 396 | Entonces, creamos la carpeta models, y ahora es donde van a comenzar a notar diferencia y vamos a aprender lo que son los decoradores. 397 | En la carpeta models creamos User.ts y tambien creamos en src un archivo llamado db.ts 398 | 399 | en User.ts vamos a generar nuestro modelo con la libreria sequelize-typescript, en vez de realizar la funcion de sequelize con sequelize.init() y sus metodos, vamos a generar una clase con decoradores! 400 | 401 | Primero ¿Qué es un decorador? 402 | Los decoradores son un patron de diseño, el cual se ejecuta atado a una funcion, una clase o una propiedad. Entonces, siempre los vamos a ver arriba de una de esas! 403 | Son muy utilizados en el framework angular y otras librerias, como sequelize-typescript, ahora vamos a ver que son muy sencillos de usar! 404 | Para usarlos lo unico que tenemos que hacer es: 405 | 406 | ```javascript 407 | @NombreDelDecorador 408 | funcion() // o el metodo o clase o atributo 409 | ``` 410 | 411 | Entonces, nos tenemos que traer de sequelize-typescript y usemos nuestro primer decorador! 412 | 413 | ```javascript 414 | import {Model, Column, Table, CreatedAt, UpdatedAt} from 'sequelize-typescript'; 415 | @Table 416 | export class User extends Model {} 417 | ``` 418 | 419 | @Table es nuestro decorador, prestemos un poco de atencion que esta sucediendo ahi. Estamos generando la clase User, que se extiende de Model. Entonces, esta herando todos los metodos de Model. 420 | ¿Y Table? ¿Que hace Table? 421 | Table hace que la clase User se ejecute de determinada manera. Condiciona como se tiene que ejecutar, en este caso, lo que hace es generarnos la tabla de postgres por nosotros! 422 | Entonces, agreguemos nuestras columnas, tambien con decoradores! 423 | 424 | ```javascript 425 | @Table 426 | export class User extends Model { 427 | @Column 428 | name!: string; 429 | 430 | @Column 431 | lastName!: string; 432 | 433 | @CreatedAt 434 | @Column 435 | createdAt!: Date; 436 | 437 | @UpdatedAt 438 | @Column 439 | updatedAt!: Date; 440 | } 441 | ``` 442 | 443 | Fijte que sencillo es! Si quisieramos agregar otros atributos, podemos agregar mas decoradores arriba o llamarlos de otra forma, @Column({//aca irian nuestros atributos}) 444 | Asi mismo, tambien dentro de la clase deberiamos generar nuestras relaciones! No en un archivo afuera, te invito a que leas la documentacion de sequelize-typescript para que veas como se hace! Es muy sencillo. 445 | 446 | Ahora, en nuestro archivo db.ts, tenemos que generar nuestra conexion con la db! 447 | 448 | ```javascript 449 | import {Sequelize} from 'sequelize-typescript'; 450 | import config from './lib/config'; 451 | config; 452 | export const sequelize = new Sequelize({ 453 | dialect: 'postgres', 454 | database: config.dbName, 455 | password: config.dbPassword, 456 | username: config.dbUser, 457 | storage: ':memory:', 458 | models: [__dirname + '/models'], 459 | }); 460 | ``` 461 | 462 | Como ven, tienen que generar un nuevo Sequelize, pasarle esa informacion en un objeto! 463 | Advertencia: Si la carpeta no se llama models, no va a funcionar, y cada archivo dentro de la carpeta models debe tener la primer letra en mayuscula. 464 | Ahora lo único que nos falta es inicializar nuestro sequelize y sincronizar la base de datos! 465 | Volvemos a nuestro index.ts de la carpeta raiz y generamos nuestra conexion. 466 | 467 | ```javascript 468 | import {sequelize} from './src/db'; 469 | import app from './src/app'; 470 | sequelize 471 | .sync({force: true, logging: false}) 472 | .then(() => { 473 | console.log('base de datos conectada! :D'); 474 | app.listen(3001, function () { 475 | console.log('App is listening on port 3001!'); 476 | }); 477 | }) 478 | .catch((err) => console.error(err)); 479 | ``` 480 | 481 | Entonces, lo unico que nos falta seria agregar a nuestras rutas la conexion con nuestros modelos! Lo hacemos de forma muy parecido a como lo hacemos con javascript normal! 482 | 483 | ```javascript 484 | router.get('/', (req: Request, res: Response, next: NextFunction) => { 485 | User.findAll() 486 | .then((users) => { 487 | res.send(users); 488 | }) 489 | .catch((error) => next(error)); 490 | }); 491 | 492 | router.post('/', (req: Request, res: Response, next: NextFunction) => { 493 | const user = req.body; 494 | User.create(user) 495 | .then((createdUser) => { 496 | res.send(createdUser); 497 | }) 498 | .catch((error) => next(error)); 499 | }); 500 | ``` 501 | 502 | De esta forma, nuestro back quedaria completo. Como pueden ver, es simplemente javascript con unos pasitos extra! 503 | Ahora vamos a ver a nuestro front-end! 504 | 505 | ## Front-end 506 | 507 | ### Dependecias 508 | 509 | En las dependencias del package.json del boilerplate ya van a estar incluidas `redux`, `react-redux`, `redux-thunk` y `axios` por lo que simplemente con el `npm install` del comienzo ya alcanzaría, pero si comienzan desde cero en un proyecto separado recuerden que deben instalar las mismas. 510 | 511 | ```bash 512 | npm install --save redux react-redux redux-thunk axios 513 | ``` 514 | 515 | #### Component App 516 | 517 | Crearemos una carpeta llamada `components` para poner allí todo los componentes que vayamos a necesitar para la aplicación. 518 | 519 | Comenzaremos con el componente principal de la aplicación al que llamaremos `App` (recordar la extensión tsx), como ya les explicamos anteriormente como crearlos no tendrán el código para esta parte... tendrán que hacerlo ustedes. La idea es que por el momento el componente simplemente retorne un `
` con el texto que deseen. 520 | 521 | Ahora hagamos que en nustro `index.tsx` se importe dicho componente para poder utilizarlo (Recordar exportar el componente en `App.tsx` para poder hacer este paso): 522 | 523 | ```javascript 524 | import App from './components/App'; // Importante no poner la extensión .tsx sino arrojara un error 525 | 526 | ReactDOM.render(, document.querySelector('#root')); 527 | ``` 528 | 529 | #### Store 530 | 531 | Crearlo en un archivo separado dentro de una carpeta llamada `store` e importarlo para poder utilizarlo. Ya saben como hacerlo, así que no habrá código acá. Recuerden también agregarle redux-thunk para poder realizar los request desde las actions creators. 532 | 533 | #### Reducer 534 | 535 | Recuerden también de generar un archivo separado para el reducer que utilizará la app, por prolojidad conviene crear una carpeta nueva `reducers` y allí agregar el archivo `index.ts`. Inicialmente el estado debe contener un objeto con una única propiedad `counter` con valor 1. 536 | 537 | Si lo hacemos como ya sabíamos de React: 538 | 539 | ```javascript 540 | const initialState = { 541 | counter: 1, 542 | }; 543 | 544 | export default function reducer(state = initialState, action) { 545 | return state; 546 | } 547 | ``` 548 | 549 | Nos va a arrojar el siguiente error: 550 | 551 | ![screen2](./img-screens/2.png) 552 | 553 | Nuevamente debido al chequeo de tipos que realiza Typescript vamos a tener que hacer algunos ajustes a nuestro reducer. Debemos definir las interfaces para nuestro estado y para nuestras actions. Podemos hacerlo directaente en ese mismo archivo del reducer. 554 | 555 | ```javascript 556 | interface stateI { 557 | counter: number; 558 | } 559 | 560 | const initialState: stateI = { 561 | counter: 1, 562 | }; 563 | 564 | interface actionI { 565 | type: string; 566 | } 567 | 568 | export default function reducer(state: stateI = initialState, action: actionI) { 569 | return state; 570 | } 571 | ``` 572 | 573 | Para que lo tengan en cuenta a veces se suele tener un archivo separado para definir las interfaces y simplemente importarlas donde las necesitemos, ya que es probable que algunas tengamos que reutilizarlas en más de un archivo. Quizás más adelante tengamos que hacer dicho cambio. 574 | 575 | #### Provider 576 | 577 | Comenzaremos configurando el componente `` dentro de nuestro `index.tsx` para permitir el acceso al store desde cualquier componente de la aplicación: 578 | 579 | ```javascript 580 | import React from 'react'; 581 | import ReactDOM from 'react-dom'; 582 | import {Provider} from 'react-redux'; 583 | 584 | ReactDOM.render( 585 | 586 |

Henry

587 |
, 588 | document.querySelector('#root') 589 | ); 590 | ``` 591 | 592 | ¿Todo OK? Nop, Typescript otra vez... 593 | 594 | ![screen4](./img-screens/4.png) 595 | 596 | Para que el módulo de `react-redux` funcione correctamente con Typescript debemos adicionalmente instalar un módulo separado en donde están definido los tipos de datos. Si observamos en el mensaje de error incluso nos dice como hacerlo. 597 | 598 | ```bash 599 | npm i --save-dev @types/react-redux 600 | ``` 601 | 602 | #### Action Creators 603 | 604 | Crearemos una carpeta denominada `actions` y dentro el archivo `index.ts`. Allí colocaremos todo el código relacionado con las action creators. 605 | 606 | Primero vamos a importar `axios` ya que lo vamos a necesitar para hacer los request a la API. Luego creremos una action creator que se encargue de ir a buscar todos las publicaciones (Las llamaremos `user`) de la API: 607 | 608 | ```javascript 609 | import axios from 'axios'; 610 | 611 | const url = 'https://localhost:3001/user'; 612 | 613 | export const fetchUsers = () => { 614 | return async (dispatch) => { 615 | const response = await axios.get(url); 616 | dispatch({ 617 | type: 'FETCH_USERS', 618 | payload: response.data, 619 | }); 620 | }; 621 | }; 622 | ``` 623 | 624 | ![screen6](./img-screens/6.png) 625 | 626 | ![screen7](./img-screens/7.jpeg) 627 | 628 | A no desesperar, nuevamente lo que está ocurriendo es que debemos indicarle la estructura de datos al dispatch pero ahora ¿cómo hacemos si no conocemos exactamente como está implementada por dentro? 629 | 630 | Por suerte podemos directamente del módulo de `redux` importar la estructura de tipos del dispatch: 631 | 632 | ```javascript 633 | import { Dispatch } from 'redux'; 634 | 635 | export const fetchUsers = () => { 636 | return async (dispatch: Dispatch) => { 637 | ... 638 | }; 639 | }; 640 | ``` 641 | 642 | ¿Qué estructura de datos tiene la respuesta al llamado a la API? 643 | 644 | De antemano no podemos saberlo por lo que va a ser del tipo `any`. Por otro lado dentro del objeto que estamos despachando tampoco indicamos el tipo de dato ni del `type` ni del `payload`. El programa está asumiendo que vamos a pasarle un `string` y `any` respectivamente. 645 | 646 | Vamos a mejorar un poco esto definiendo la estructura que debería tener la respuesta al GET. Para eso veamos que nos está devolviendo dicho endpoint: 647 | 648 | ![screen8](./img-screens/8.png) 649 | 650 | Como pueden observar en la imagen deberíamos tener: 651 | 652 | - id 653 | - name 654 | - lastName 655 | 656 | Por lo tanto crearemos una interfaz con esa estructura (Omitiremos el `userId` ya que no lo usaremos en nuestra aplicación): 657 | 658 | ```javascript 659 | interface User { 660 | id: number; 661 | name: string; 662 | lastName: string; 663 | } 664 | ``` 665 | 666 | Y ahora debemos aclarar que el método get de axios espera recibir algo con esa estructura (Un arreglo de `User`): 667 | 668 | ```javascript 669 | ... 670 | const response = await axios.get(url); 671 | ... 672 | ``` 673 | 674 | Acá entra en juego lo que vimos en la clase de `Generics`, ya que axios tiene definida la función `get` de la siguiente forma: 675 | 676 | ```javascript 677 | ... 678 | get>(url: string, config?: AxiosRequestConfig): Promise; 679 | ... 680 | ``` 681 | 682 | [Link](https://github.com/axios/axios/blob/master/index.d.ts#L140) a la documentación de axios donde expone sus interfaces. Incluso podriamos ver como es la estructura completa de la respuesta buscando ese `AxiosResponse`: 683 | 684 | ```javascript 685 | export interface AxiosResponse { 686 | data: T; 687 | status: number; 688 | statusText: string; 689 | headers: any; 690 | config: AxiosRequestConfig; 691 | request?: any; 692 | } 693 | ``` 694 | 695 | Allí veremos que dentro del response, y particularmente en la propiedad `data` tendremos algo que se corresponda con el tipo de dato que le indiquemos que en nuestro caso debería ser un `User[]`. Si no lo indicabamos estaba asumiendo que podía ser cualquier cosa (`any`). 696 | 697 | Ahora crearemos una forma de tener de forma prolija todos los tipos de las actions que vamos a querer despachar. En este caso para ello crearemos un `Enum` dentro de un nuevo archivo `types.ts` en la carpeta `actions`. 698 | 699 | ```javascript 700 | export enum ActionTypes { 701 | fetchUsers, 702 | ... 703 | } 704 | ``` 705 | 706 | Debajo de `fetchUsers` iremos completando con los types que queramos poder tener dentro de nuestras actions. 707 | 708 | **Explicación sobre el funcionamiento de enum**: Por defualt si por ejemplo a fetchUsers no le indicamos ningún valor va a asignarle el número 0 y así sucesivamente hacía abajo aumentando el valor del número como si fuera un índice. Por el contrario si quisiéramos que si o si tenga un valor en formato texto deberíamos ponerlo como `fetchUsers = "FETCH_USERS"` pero a nosotros no nos va a interesar que valor tenga sino que redux pueda identificar uno de otros por lo que lo dejaremos como estaba más arriba. 709 | 710 | Ahora importamos el `ActionTypes` en el `index.ts` de las actions y lo reemplazamos por el string que teniamos hardcodeado. 711 | 712 | ```javascript 713 | import { ActionTypes } from './types'; 714 | 715 | ... 716 | 717 | export const fetchUsers = () => { 718 | return async (dispatch: Dispatch) => { 719 | const response = await axios.get(url); 720 | dispatch({ 721 | type: ActionTypes.fetchUsers, 722 | payload: response.data 723 | }) 724 | }; 725 | }; 726 | ``` 727 | 728 | **Paso "opcional"**: Es una buena práctica que va a mejorar un poco el chequeo de tipos pero no necesariamente deben hacerlo en sus aplicaciones de typescript pero en este workshop haganlo porque lo necesitaremos por como vamos a implementar algunas cuestiones más adelante. 729 | 730 | Crearemos una nueva interfaz para describir el objeto (action) dentro del dispatch: 731 | 732 | ```javascript 733 | ... 734 | 735 | interface FetchUsersAction { 736 | type: ActionTypes.fetchUsers; 737 | payload: User[]; 738 | } 739 | 740 | export const fetchUsers = () => { 741 | return async (dispatch: Dispatch) => { 742 | const response = await axios.get(url); 743 | dispatch({ 744 | type: ActionTypes.fetchUsers, 745 | payload: response.data 746 | }) 747 | }; 748 | }; 749 | ``` 750 | 751 | Esto nos va a asegurar que en el dispatch estemos pasando un objeto con la estructura correcta. 752 | 753 | #### Reducer 754 | 755 | Volvemos a trabajar un poco más sobre nuestro reducer, pero lo que teníamos antes del `counter` ya no va a servirnos por lo que lo sacaremos y esta vez configuraremos los reducers basándonos en la idea que más adelante tendremos más por lo tanto utilizaremos el combineReducers. 756 | 757 | Algunos cambios que tienen que hacer: 758 | 759 | - Opcional: cambiar el import en store para que se llame `reducers` (en plural) porque utilizaremos un combineReducers ahora. Esto simplemente es para ser más representativos con los nombres que utilizamos pero si no lo cambian va a seguir funcionando correctamente: `import { reducers } from '../reducers/index';` 760 | - Modificaremos el archivo de reducer momentáneamente por lo siguiente: 761 | 762 | ```javascript 763 | import {combineReducers} from 'redux'; 764 | 765 | export const reducers = combineReducers({ 766 | counter: () => 1, 767 | }); 768 | ``` 769 | 770 | Ahora vamos vamos a modificarlo para adaptarlo a la aplicación de "Users". Primero vamos a un archivo `users.ts` en la carpeta `reducers` e importar la interfaz de User desde actions para ello no olviden agregarle un `export`. 771 | 772 | ```javascript 773 | import {User, FetchUsersAction} from '../actions'; 774 | 775 | export const usersReducer = ( 776 | state: User[] = [], 777 | action: FetchUsersAction 778 | ) => {}; 779 | ``` 780 | 781 | Ahí estamos definiendo nuestro reducer para los Users y describiendo que estructura de datos debería tener tanto el state como la action. 782 | 783 | ¿Qué problema podría llegar a tener esa implementación? Bueno, piensen que pasaría si la action que llega no es estrictamente igual a la de `FetchUsersAction`. Más adelante veremos como mejorar esto, por ahora dejemoslo así que va a funcionar. 784 | 785 | Ahora agregaremos la lógica dentro del reducer. Vamos a tener que importar también los `ActionTypes` para hacer el switch o if's dentro del reducer: 786 | 787 | ```javascript 788 | import {User, FetchUsersAction} from '../actions'; 789 | import {ActionTypes} from '../actions/types'; 790 | 791 | export const usersReducer = (state: User[] = [], action: FetchUsersAction) => { 792 | switch (action.type) { 793 | case ActionTypes.fetchUsers: 794 | return action.payload; 795 | default: 796 | return state; 797 | } 798 | }; 799 | ``` 800 | 801 | Ahora vamos a meter este reducer que acabamos de crear dentro del combineReducers en nuestro archivo `index.ts` de la carpeta `reducers`: 802 | 803 | ```javascript 804 | import {combineReducers} from 'redux'; 805 | import {usersReducer} from './users'; 806 | 807 | export const reducers = combineReducers({ 808 | users: usersReducer, 809 | }); 810 | ``` 811 | 812 | **Recordatorio**: el estado de redux cuando usamos un combineReducers quedaría de la siguiente forma 813 | 814 | ```javascript 815 | { 816 | users: [User, User]; // Donde User sería un objeto del tipo User 817 | } 818 | ``` 819 | 820 | Podemos también validar la estructura de datos de nuestro store, nuevamente este paso es opcional, ya que va a funcionar de todas formas pero le damos más robustez a nuestro código al hacerlo. Para esto crearemos una interfaz que va a representar la estructura de datos de todo nuestro store. 821 | 822 | ```javascript 823 | import {combineReducers} from 'redux'; 824 | import {usersReducer} from './users'; 825 | import {User} from '../actions'; 826 | 827 | export interface StoreState { 828 | users: User[]; 829 | } 830 | 831 | export const reducers = 832 | combineReducers < 833 | StoreState > 834 | { 835 | users: usersReducer, 836 | }; 837 | ``` 838 | 839 | #### Conectar componente App con Redux 840 | 841 | ```javascript 842 | import React from 'react'; 843 | import {connect} from 'react-redux'; 844 | import {User, fetchUsers} from '../actions'; 845 | import {StoreState} from '../reducers'; 846 | 847 | interface AppProps { 848 | users: User[]; 849 | fetchUsers(): any; 850 | } 851 | 852 | function App(props: AppProps) { 853 | return
Hello Henrys
; 854 | } 855 | 856 | const mapStateToProps = (state: StoreState): {users: User[]} => { 857 | return { 858 | users: state.users, 859 | }; 860 | }; 861 | 862 | export default connect(mapStateToProps, {fetchUsers})(App); 863 | ``` 864 | 865 | #### Mostrar algun User 866 | 867 | En primer lugar vamos a agregar el `useEffect` que ya conocemos para despachar la acción al renderizar el componente: 868 | 869 | ```javascript 870 | useEffect(() => { 871 | props.fetchUsers(); 872 | }, []); 873 | ``` 874 | 875 | Si observamos las request enviadas al abrir nuestra aplicación veremos que efectivamente el request para solicitar los USERS a la API están funcionando: 876 | 877 | ![screen9](./img-screens/9.png) 878 | 879 | **OPCIONAL**: Vamos a configurar redux devtools para poder ver y debuggear nuestra aplicación. 880 | 881 | ```javascript 882 | import { createStore, applyMiddleware, compose } from 'redux'; 883 | import { reducers } from '../reducers/index'; 884 | import thunk from 'redux-thunk'; 885 | 886 | declare global { 887 | interface Window { 888 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose; 889 | } 890 | } 891 | 892 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 893 | 894 | const store = createStore( 895 | reducers, 896 | composeEnhancers(applyMiddleware(thunk)) 897 | ); 898 | 899 | export default store; 900 | ``` 901 | 902 | ![screen10](./img-screens/10.png) 903 | 904 | Recuerden que el nombre de la acción figura como un 0 debido a que utilizamos un Enum con sus valores por defecto, si ustedes le asignaron un String van a ver dicho nombre allí. 905 | 906 | Ahora que sabemos que funciona hagamoslo de la forma correcta, a partir de un botón que al hacerle click recién allí despache dicha acción. 907 | 908 | ```javascript 909 | function App(props: AppProps) { 910 | return ( 911 |
912 | 913 | {props.users.map((user: User) => { 914 | return ( 915 |
916 | {user.id}) {user.name} 917 |
918 | ); 919 | })} 920 |
921 | ); 922 | } 923 | ``` 924 | 925 | Con eso ya tendríamos un listado de nuestros Users 926 | 927 | ![screen11](./img-screens/11.png) 928 | 929 | #### Eliminar algun Users 930 | 931 | Para esta funcionalidad no habrá ningún tipo de ayuda, en función de todo lo que vimos hasta ahora deberán agregar un botón por cada User para que al hacerle click elimine dicho User del listado total. 932 | 933 | ![screen12](./img-screens/12.jpg) 934 | 935 | **AYUDA**: Para cuando tengan que en el reducer aceptar más de un tipo de action pueda hacer una unión utilizando al caracter `|`. Por ejemplo: 936 | 937 | ```javascript 938 | export const userssReducer = ( 939 | state: User[] = [], 940 | action: FetchUsersAction | DeleteUsersAction 941 | ) => { 942 | switch (action.type) { 943 | ... 944 | } 945 | }; 946 | ``` 947 | 948 | ¿Pero que pasaría si se agregan muchos types distintos? Crecería mucho esa parte del código y quedaría poco legible. Por lo que hay una mejor forma de hacerlo, encapsularemos esa lógica dentro del archivo `types.ts` dentro de `actions`: 949 | 950 | ```javascript 951 | import { FetchUsersAction, DeleteusersAction } from './index'; 952 | 953 | export enum ActionTypes { 954 | fetchUserss, 955 | deleteUsers 956 | } 957 | 958 | export type Action = FetchUsersAction | DeleteUsersAction; 959 | ``` 960 | 961 | Por lo que nuestro reducer ahora quedaría así: 962 | 963 | ```javascript 964 | import { User } from '../actions'; 965 | import { ActionTypes, Action } from '../actions/types'; 966 | 967 | export const usersReducer = ( 968 | state: User[] = [], 969 | action: Action 970 | ) => { 971 | switch (action.type) { 972 | ... 973 | } 974 | }; 975 | ``` 976 | 977 | #### Extra: Loading 978 | 979 | Adicionalmente para mejorar un poco la UX de nuestra paǵina sería bueno tener al menos una palabra "Loading..." cuando el usuario le de click al botón de "FETCH USERS!" por si el request demora un poco. 980 | 981 | Implementar dicha funcionalidad con lo visto hasta el momento. 982 | 983 | ![screen13](./img-screens/13.jpg) 984 | --------------------------------------------------------------------------------