11///-- INTERFACES **********************************************************************************
22
3- // Una interfaz no es más que un contrato especificando las propiedades que un objeto debe tener.
4- // Uno de los objetivos más básicos que tienen las interfaces es que nos permiten dar nombres a
5- // las estructuras y por lo tanto podemos reusarlos .
3+ // Una interfaz no es más que un contrato especificando las propiedades de un objeto: cómo deben
4+ // llamarse dichas propiedades y de que tipo deben ser. El objetivo de los interfaces, además de
5+ // tipar objetos, es darle nombre a las estructuras y poder reutilizarlos .
66
7- interface Geoposition {
8- latitude : number ;
9- longitude : number ;
7+ interface Coord {
8+ lat : number ;
9+ lon : number ;
1010}
1111
12- const pos : Geoposition = {
13- latitude : 3.3112 ,
14- longitude : 5.1123 ,
12+ const pos : Coord = {
13+ lat : 3.3112 ,
14+ lon : 5.1123 ,
1515} ;
1616
1717// En caso de no especificar todas las propiedades lanzará un error de compilación
18- const pos1 : Geoposition = {
19- // ^^^^ [ts] Type '{ latitude: number; } ' is not assignable to type 'Geoposition '.
20- latitude : 3.3112 ,
18+ const pos : Coord = {
19+ // ^^^^ [ts] Property 'lon ' is missing in type '{ lat: number; } '.
20+ lat : 3.3112 ,
2121} ;
2222
2323// También si especificamos propiedades por exceso, lanzaría un error.
24- const pos2 : Geoposition = {
25- latitude : 3.3112 ,
26- longitude : 5.1123 ,
27- altitude : 325 ,
24+ const pos : Coord = {
25+ lat : 3.3112 ,
26+ lon : 5.1123 ,
27+ alt : 325 , // [ts] Object literal may only specify known properties, and 'alt' does not exist in
28+ // type 'Coord'
2829} ;
2930
30- // Podemos ANIDAR, es decir, utilizar interfaces como tipos dentro de otras inferfaces:
31+ // *** Modificadores: optional & readonly *********************************************************
32+
33+ // TypeScript soporta propiedades opcionales mediante el operador [?]. Una propiedad opcional es
34+ // aquella que puede ser declarada (y por tanto tendrá un valor asignado) o no. Si no la declaramos,
35+ // su valor sería undefined.
36+
37+ // Además, podemos forzar a que ciertas propiedades sean de sólo lectura. Para ello usaremos el
38+ // operador "readonly" delante de la propiedad. Con esto conseguimos establecer el valor de la
39+ // propiedad a la hora de crear el objeto denegando la posibilidad (a nivel de compilación) de
40+ // reasignar dicha propiedad.
41+
42+ interface Coord {
43+ readonly lat : number ;
44+ readonly lon : number ;
45+ alt ?: number ;
46+ }
47+
48+ const pos : Coord = {
49+ lat : 3.3112 ,
50+ lon : 5.1123 ,
51+ // alt: 350, // Optional
52+ } ;
53+
54+ pos . lat = 3.4567 ; // [ts] Cannot assign to 'lat' because it is a read-only property
55+
56+
57+ // *** Anidamiento y extensión de interfaces ******************************************************
58+
59+ // Podemos anidar interfaces dentro de otros. Esta es una forma jerárquica de componer interfaces,
60+ // por ejemplo:
61+
3162interface Address {
3263 zipCode : number ;
3364 city : string ;
3465 street : string ;
3566}
3667
37- interface Citizen {
38- name : string ;
68+ interface Coord {
69+ lat : number ;
70+ lon : number ;
3971 address : Address ;
4072}
4173
42- const citizen : Citizen = {
74+ const place : Coord = {
75+ lat : 3.3112 ,
76+ lon : 5.1123 ,
4377 address : {
4478 city : "Málaga" ,
4579 street : "Héroes de Sostoa" ,
4680 zipCode : 29002 ,
4781 } ,
48- name : "Javier" ,
4982} ;
5083
51- // *** Propiedades Opcionales
52- // TypeScript soporta propiedades opcionales mediante el operador [?]. Una propiedad opcional no es
53- // más que aquella cuyo valor puede ser ser definido o no (en este segundo caso sería undefined).
84+ // También es frecuente utilizar la extensión de interfaces como método de composición. Es decir,
85+ // una interfaz puede extender de otra para combinar propiedades:
5486
55- // Podemos definir propiedades opcionales de una interfaz
56- interface Book {
57- isbn : number ;
58- author ?: string ;
59- }
60- const book1 : Book = { isbn : 764589621 } ;
61- console . log ( book1 . author ) ; // undefined
62- const book2 : Book = { isbn : 854632187 , author : "Rolan" } ;
63- console . log ( book2 . author ) ; // Rolan
64-
65- // *** Propiedades Read-Only
66- // Podemos forzar a que ciertas propiedades sean de sólo lectura. Para ello usaremos el operador
67- // "readonly" delante de la propiedad. Con esto conseguimos establecer el valor de la propiedad a
68- // la hora de crear el objeto denegando la posibilidad (a nivel de compilación) de reasignar
69- // dicha propiedad:
70- interface Product {
71- readonly id : number ;
72- stock : number ;
87+ interface Address {
88+ zipCode : number ;
89+ city : string ;
90+ street : string ;
7391}
74- const product : Product = { id : 998 , stock : 0 } ;
75- product . stock = 20 ;
76- product . id = 339 ; // [ts] Cannot assign to 'id' because it is a constant or a 'read-only' property
7792
78- // Extendiendo interfaces
79- interface MyEvent {
80- type : string ;
93+ interface Coord {
94+ lat : number ;
95+ lon : number ;
8196}
8297
83- interface MyKeyboardEvent extends MyEvent {
84- key : string ;
98+ interface Place extends Coord {
99+ address : Address ;
85100}
86101
87- const keyboardEvent : MyKeyboardEvent = {
88- key : "shift" ,
89- type : "keyboard event" ,
102+ const place : Place = {
103+ lat : 3.3112 ,
104+ lon : 5.1123 ,
105+ address : {
106+ city : "Málaga" ,
107+ street : "Héroes de Sostoa" ,
108+ zipCode : 29002 ,
109+ } ,
90110} ;
91111
92- // Podemos extender de varias interfaces a la vez, esto es posible ya que las interfaces sólo
93- // especifican las propiedades que un objeto debe de tener (a pesar de usar extends no tiene
94- // por qué estar relacionado con la herencia prototípica)
112+ // Incluso sería posible la extensión múltiple:
113+ interface Place extends Coord , Address { }
95114
96- interface Soldier {
97- swordType : string ;
98- }
115+ const place : Place = {
116+ lat : 3.3112 ,
117+ lon : 5.1123 ,
118+ city : "Málaga" ,
119+ street : "Héroes de Sostoa" ,
120+ zipCode : 29002 ,
121+ } ;
99122
100- interface Ranger {
101- ammoType : string ;
102- totalAmmo : number ;
103- }
123+ // *** Duck typing ********************************************************************************
104124
105- interface Ninja extends Soldier , Ranger {
106- smokeBombs : number ;
107- }
125+ // TypeScript no tan estricto a la hora de tipar como pudiera parecer a priori y no ejecuta un
126+ // chequeo semántico de los tipos sino que hace una comparación estructural.
108127
109- const ninja : Ninja = {
110- swordType : "katana" ,
111- ammoType : "shuriken" ,
112- totalAmmo : 10 ,
113- smokeBombs : 3 ,
114- } ;
128+ // Es decir, al trabajar con interfaces, TypeScript NO comprueba que un objeto o instancia es de
129+ // tipo "X" (comprobación semántica) sino que comprueba que su estructura es compatible con el
130+ // contrato que indica el tipo "X" (comprobación estructural).
131+
132+ // Como curiosidad, el nombre de duck typing viene de un antiguo dicho: "Si camina como un pato,
133+ // nada como un pato y hace cuack, entonces es que probablemente sea un pato".
134+
135+ // Vemos el siguiente ejemplo:
115136
116- // *** Duck typing
117- // TypeScript no tan estricto a la hora de tipar y no tiene en cuenta semánticamente los tipos,
118- // sino que sigue una comparación estructural. Vemos el siguiente ejemplo:
119137interface User {
120138 id : number ;
121139 name : string ;
@@ -147,29 +165,36 @@ printUserName(julia);
147165// Pero tampoco hay problemas a la hora de utilizarlo con un objeto de tipo Dog
148166printUserName ( laika ) ;
149167
150- // Esto es, por lo que decimos, User y Dog comparten la misma estructura, si intentamos usarlo con
151- // otra interfaz que no comparta sus mismos atributos dará un error de tipos
168+ // ¿Cómo es posible que pueda pasar como argumento 'user' un objeto de tipo Dog? Porque, aun siendo
169+ // objetos de tipos diferentes, los interfaces User y Dog comparten la misma estructura.
170+ // Y TypeScript no hace un chequeo semántico, es decir, no comprueba que el argumento user tenga que
171+ // estar forzosamente tipado con el interface User sino que comprueba que su estructura encaja con
172+ // lo especificado en dicho interface.
173+
174+ // Si intentamos usarlo con otra interfaz que no comparta sus mismos atributos dará un error
175+ // de tipos:
176+
152177interface Cat {
153178 name : string ;
154179}
155180
156181const garfield : Cat = { name : "Garfield" } ;
182+
183+ // Garfield no tiene "id: number" por lo que no es compatible con el tipo esperado para
184+ // el argumento user
157185printUserName ( garfield ) ;
158- // Garfield no tiene "id: number" por lo que no compatible con el tipo que necesita
159186
160- // Como vemos si la interfaz no tiene las propiedades requeridas no podrá ser usado por la función.
161- // Sin embargo esto no significa que la interfaz pueda tener más propiedades
187+ // Como vemos, si la interfaz no tiene, como mínimo, las propiedades requeridas no podrá ser usada
188+ // como argumento de la función.
189+ // Sin embargo, si dispone de las propiedades necesarias cómo mínimo, aunque tenga más propiedades,
190+ // si que podría validarse debido al duck typing:
162191
163- interface Fish {
192+ interface Cat {
164193 id : number ;
165194 name : string ;
166- colors : string [ ] ;
195+ color : string ;
167196}
168197
169- const bob : Fish = { id : 319 , name : "Bob" , colors : [ "white" , "yellow" ] } ;
170- printUserName ( bob ) ;
171- // bob al tener "id: number" y "name: string" es compatible, pese a tener más propiedades de
172- // las requeridas
198+ const garfield : Cat = { id : 43 , name : "Garfield" , color : "orange" } ;
173199
174- // Es decir, actualmente la función printUserName necesita un objeto que estructuralmente tenga
175- // como mínimo las dos propiedades de User,
200+ printUserName ( garfield ) ; // OK!
0 commit comments