====== Concepts centraux de TypeScript ====== PeerTube est développé à l'aide de [[http://www.typescriptlang.org/index.html|TypeScript]], un langage apportant des fonctionnalités supplémentaires à JavaScript. Conçu par Anders Hejlsberg (concepteur de C#, chez Microsoft), le but du langage est de permettre de développer des applications à plus grande échelle et d'augmenter la fiabilité et la maintenabilité du code. TypeScript offre des possibilités qui reposent sur les concepts centraux suivants. ===== Le static-typing ===== Le premier apport de TypeScript, ce qui lui vaut son nom, est le typage. ==== Variables ==== Il est possible d'associer un type à une donnée. On retrouve trois types : ''number'', ''string'' et ''boolean'', respectivement pour les nombres entiers et flottants, les chaînes de caractères et les booléens. Lors de l'initialisation d'une variable, TypeScript **infère automatiquement son type s'il n'a pas été déclaré**. Lorsqu'il ne parvient pas à le déterminer il utilise le type ''any''. Une déclaration de variables se fait comme suit : var page: number = 7; // annotation de type var completeVideoDescription: string; var remoteServerDown = true; // boolean (implicite) var joker: any; var joker2; // any ==== Fonctions ==== On peut indiquer le **type du retour** d'une fonction : function helloWorld():string { return "Hello World"; } ==== Intérêt ==== Cette possibilité de déclarer explicitement le type d'une variable est nommée **//static-typing//**. Mais l'intérêt, c'est qu'on donne plus d'informations sur notre programme, qui vont permettre au //type-checker// de TypeScript de **signaler les erreurs ou oublis**. Sur ce point, JavaScript était permissif, ce qui pouvait occasionner des erreurs qu'on met du temps à débugger. ===== Concepts de l'Orienté-Objet ===== TypeScript complète JavaScript avec des éléments de la [[https://fr.wikipedia.org/wiki/Programmation_orient%C3%A9e_objet|programmation orientée-objet]]. ==== Les interfaces ==== Supposons qu'on ait un objet ''user'', défini par son ''firstName'' et ''lastName''. On veut que tous les ''users'' possèdent toujours ces deux propriétés. Les interfaces, **en définissant la forme que doivent avoir les objets**, permettent de s'en assurer : interface User { firstName: string; lastName: string; } function greeter(user: User):string { return "Hello, " + user.firstName + " " + user.lastName; } let user = { firstName: "John", lastName: "Doe", age: 20 }; document.body.innerHTML = greeter(user); Le //type-checker// de TypeScript **va vérifier** que l'objet ''user'' passé à la fonction''greeter'' correspond bien à l'interface ''User''. Deux choses à remarquer : * on peut ne pas déclarer explicitement que ''user'' implémente l'interface ''User'', comme c'est le cas en PHP avec la clause ''implements'' (on verra avec [[txs:contrib:peertube_a18:concepts_typescript#heritage|les classes]] un exemple d'utilisation de cette clause); * ''user'' possède en plus une propriété ''age'' mais le //type-checker// vérifie juste que les propriétés présentes dans l'interface sont bien là. On trouvera souvent ces interfaces déclarées en haut des fichiers de code. A la différence d'une structure en C, **une interface n'est pas instanciable**, elle permet le contrôle de la structuration d'une variable ou d'une instance de classe. Cela fonctionne en revanche comme en Java, où l'on instancie des classes, mais pas des interfaces. **Une interface n'est pas une classe**. Ce que fait l'interface, c'est décrire la forme des objets. Ce que fait la classe, c'est implémenter la création des objets. ---- Dans le code de **PeerTube**, les interface sont rangées dans le dossier ''shared/models/videos/''. ==== Les classes ==== === Déclaration === Les classes en TypeScript se déclarent comme dans l'exemple suivant : class VideoWatchComponent { //// attributs player: videojs.Player completeDescriptionShown = false completeVideoDescription: string shortVideoDescription: string videoHTMLDescription = '' ... //// constructeur constructor( constr_videoHTMLDescription: string, private elementRef: ElementRef, private changeDetector: ChangeDetectorRef, private route: ActivatedRoute, private router: Router, ... ) { this.videoHTMLDescription = constr_videoHTMLDescription; } //// méthodes // un getter : get user () { return this.authService.getUser() } // un setteur : setLike () { if (this.isUserLoggedIn() === false) return if (this.userRating === 'like') { // Already liked this video this.setRating('none') } else { this.setRating('like') } } // une autre fonction de la classe : showMoreDescription () { if (this.completeVideoDescription === undefined) { return this.loadCompleteDescription() } this.updateVideoDescription(this.completeVideoDescription) this.completeDescriptionShown = true } } Cet exemple est basé sur la classe déclarée dans le fichier ''client/src/app/videos/+video-watch/video-watch.component.ts''. Une déclaration de classe se structure donc par des **attributs** (variables définies sans le mot clé ''var''), un **constructeur** et des **méthodes** (fonctions définies sans le mot clé ''function''). Attributs et méthodes peuvent être de 4 types différents : * ''public'' : c'est le type implicite, il signifie que n'importe qui peut accéder à l'élément en question ; * ''private'' : non implicite, il rend un élément non accessible depuis l'extérieur ou même par une sous-classe ; * ''protected'' : fonctionne comme ''private'' mais permet aux sous-classes d'accéder à l'élément ; * ''readonly'' : l'élément est défini une fois pour toutes au moment de la déclaration de la classe et n'est plus modifiable par la suite. === Instanciation === Pour instancier la classe déclarée précédemment, on peut procéder comme suit : var ma_classe = new VideoWatchComponent("Description de la vidéo..."); Cette instanciation permet grâce au constructeur de d'initaliser l'attribut ''videoHTMLDescription''. Pour y accéder, on pourrait utiliser deux méthodes : * L'accès direct à l'attribut en question, s'il n'est pas ''private'' : ma_classe.videoHTMLDescription; * L'accès par une méthode getter (dans l'optique où il a été déclaré) : ma_classe.getVideoHTMLDescription(); === Héritage === De pair avec la notion de classe, TypeScript implémente la notion d'**héritage simple** par l'utilisation du mot-clé ''extends''. Dans le fichier ''/client/src/app/videos/video-list/video-trending.component.ts'', la classe ''VideoTrendingComponent'' hérite de la classe abstraite ''AbstractVideoList'' class VideoTrendingComponent extends AbstractVideoList { titlePage: string ... getVideosObservable (page: number) { const newPagination = immutableAssign(this.pagination, { currentPage: page }) return this.videoService.getVideos(newPagination, this.sort, undefined, this.categoryOneOf) } ... } Cette nouvelle classe ''VideoTrendingComponent'' ajoute un nouvel attribut ''titlePage'' à la classe ''AbstractVideoList'' et redéfinit la méthode ''getVideosObservable''. TypeScript permet un **pseudo-héritage multiple** en combinant classes et interfaces. Grâce au mot-clé ''implements'', une classe peut "hériter" de la structure de deux interfaces. C'est le cas pour ''VideoWatchComponent'' dans le code de PeerTube, qui hérite de ''OnInit'' et ''OnDestroy'' : class VideoWatchComponent implements OnInit, OnDestroy { ... } Comme dans d'autres langages orientés-objet, une classe peut être abstraite. On utilise le mot-clé ''abstract''. Enfin, comme en JavaScript, le mot-clé ''super'' permet à une classe-fille de référer à un élément d'une classe-mère. ===== La modularité ===== TypeScript introduit différentes techniques permettant de concevoir des applications modulaires et de grandes tailles, à l'instar de Peertube. Le concept de ''module'' y est central. Lorsque l'on déclare une variable, une classe, une fonction, //etc//. dans un fichier, celle-ci n'est visible et utilisable par un autre fichier qu'à la condition d'utiliser la mention ''export''. Inversement, pour utiliser un objet déclaré dans un autre fichier (ou ''module''), il est nécessaire de faire son ''import'' dans le module courant. ==== Export ==== Il existe deux façons, combinables, d'exporter un élément d'un module : * La mention ''export'' au début de sa déclaration ; comme pour l'interface ''Video'', déclarée dans le fichier ''./shared/models/videos/video.model.ts'' : export interface Video { id: number uuid: string createdAt: Date | string updatedAt: Date | string publishedAt: Date | string ... } * En rajoutant une ligne à cet effet après la déclaration de l'élément à exporter. On pourrait alors réécrire l'exemple précédent : /* export -> non nécessaire */ interface Video { ... } export { Video }; // ou export { Video as interfVideo }; // pour renommer l'interface lorsqu'utilisée hors de ce module Ces deux techniques sont utilisées dans le code de PeerTube de façon redondante. Le mot-clé ''export'' précède les déclarations dans les modules concernés et dans chaque sous-dossier de ''./shared'' un fichier ''index.ts'' semble gérer l'accès des modèles aux branches supérieures de l'arborescence. Ainsi, ''./shared/models/videos/index.ts'' contient les exports de tous les fichiers apparentés : export * from './rate/user-video-rate-update.model' // * signifie qu'on exporte TOUT ce que contient le module export * from './rate/user-video-rate.model' export * from './rate/user-video-rate.type' ... export * from './import/video-import-create.model' export * from './import/video-import-state.enum' export * from './import/video-import.model' export { VideoConstant } from './video-constant.model' ==== Import ==== L'importation est encore plus simple que l'exportation. On peut importer un seul élément d'un module, en rajoutant dans l'en-tête : import { VideoModel } from './video' On peut, de même qu'avec ''export'', renommer un élément importé : import { ScopeNames as VideoScopeNames, VideoModel } from './video' Il est également possible d'importer l'intégralité d'un module dans une variable, par laquelle on accède aux éléments dudit module : import * as Sequelize from 'sequelize' ... const escapedSearch = VideoModel.sequelize.escape(options.search) ... ===== Sources ===== * [[http://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html]] * [[https://www.geeksforgeeks.org/difference-between-typescript-and-javascript/]] * [[https://www.sitepoint.com/introduction-to-typescript/]] * [[https://medium.com/@khiem.office/typescript-differences-between-class-and-interface-b313f0f3dbab]] * [[https://yahiko.developpez.com/tutoriels/introduction-typescript/#L3]] * [[http://www.typescriptlang.org/docs/handbook/classes.html]] * [[https://www.tutorialspoint.com/typescript/typescript_classes.htm]]