class: center, middle background-image: url(../images/title-folie.jpeg) .title[
### Rowing Session at Fincons Group AG ### Speaker: Oleg Varaksin ] ??? - TypeScript gewinnt seit letzten Jahren immer mehr an Bedeutung, nicht zuletzt durch die modernen Web-Frameworks, wie Angular und React. - Die modernen SPAs sind heutzutage nicht ohne TS wegzudenken. - TypeScript hat sich in den vergangenen Jahren rasant entwickelt und bringt mit jedem Release immer neue Features, teilweise ausgeklügelte Features. Ich musste leider feststellen, dass die meisten Web-Entwickler, die sonst flott in Angular oder React sind, diese neuen Features nicht kennen. In den meisten TS basierten Projekten reicht ein kleines Set an Sprachkonstrukten, wie Klassen, Interfaces, Enums, Generics, Union-Typen, optionale und readonly Properties. Viel mehr wird meistens nicht benutzt. Es ist aber durchaus interessant und nützlich auch fortgeschrittene Konstukte und TS-Typen zu kennen, um sie geschickt in den TS basierten Projekten anwenden zu können. - Ziel der Präsentation: Einführung in TypeScript zu geben, aber auch tiefe Einblicke hinter die Kulissen zu gewähren. - Nach der heutigen Rowing-Session solltet ihr in der Lage sein, Software mit TypeScript zu schreiben. --- # About Speaker - __Current job__: Fullstack Senior Software-Developer / Architect at SBB - __Current project__: SmartRail 4.0, TMS-L, real-time GUIs (web and mobile) - __Main interests__: Frontend technologies: JavaScript based web frameworks, TypeScript, Angular, RxJS, WebGL, Flutter, Dart - __E-mail__: ovaraksin@googlemail.com - __GitHub__: https://github.com/ova2 - __Medium__: https://medium.com/@OlegVaraksin - __Twitter__: https://twitter.com/OlegVaraksin - __Books__: -
PrimeFaces Cookbook
, Packt Publishing -
Angular UI Development with PrimeNG
, Packt Publishing --- # Introduction - TypeScript (http://typescriptlang.org) is a typed language and a super set of JavaScript. It was designed and developed by Microsoft (Anders Hejlsberg). - TypeScript is an advanced JavaScript with optional static typing. Static typing is optional because __every JavaScript program is also a TypeScript program__. - TypeScript code is not processed by browsers, it has to be translated into JavaScript by means of a TypeScript compiler. This translation is called _compilation_ or _transpilation_. - TypeScript is an object oriented language with OO patterns. - It can be used on the client as well as server side. --- # Main advantages of TypeScript - Types help you find and fix a lot of errors during development time. That means, you have less errors at runtime. - Many modern ECMAScript features are supported out of the box. More features are expected according to the roadmap (https://github.com/Microsoft/TypeScript/wiki/Roadmap). - Great tooling and IDE support with IntelliSense makes the coding a pleasure. - It is easier to maintain and refactor a (large) TypeScript application than one written in untyped JavaScript. It makes our code more consistence, clean and reusable. - Developers feel comfortable with TypeScript due to object-oriented programming patterns, such as interfaces, classes, enums, generics, and so on. --- # What is the best way to learn the TypeScript language? _Recommended reference sources_ -
TypeScript Handbook
-
TypeScript Deep Dive by Basarat Ali Syed
-
Book: Programming TypeScript: Making Your JavaScript Applications Scale, written by Boris Cherny
-
Book: Effective TypeScript, written by Dan Vanderkam
-
Book: TypeScript Design Patterns, written by Vilic Vane
-
Design Patterns in TypeScript
--- # Relationship between TypeScript and JavaScript  - The _Typescript Language Specification_ says, every JavaScript program is also a TypeScript program. Hence, a migration from JavaScript to TypeScript code is easily done. - Converse is not true: there are TypeScript programs which are not JavaScript programs. ??? - TS bringt die Typisierung und erweitert die Sprachkonstrukte von JS um zusätzliche Syntax für spezifizierte Typen, wie beispielsweise Interfaces, Enums, usw. --- # Relationship between TypeScript and JavaScript Valid TypeScript program: ```ts function greet(who: string) { console.log('Hello', who); } ``` Result when it gets executed as an JavaScript program: ```ts function greet(who: string) { ^ SyntaxError: Unexpected token ':' ``` Reason: The `: string` is a type annotation that is specific to TypeScript. ??? Oben ist ein gültiges TS Programm. Wenn wir aber diesen Code als JS (z.B. in Browser) ausführen lassen, wird es ein SyntaxError gemeldet. Grund dafür ist : string ist eine type annotation, welche spezifisch zu TS ist. --- # Relationship between TypeScript and JavaScript Per default, TypeScript compiler __emits output (JavaScript) even when any errors are reported__. Even if your JavaScript code doesn't throw any errors, it still might not do what you intend. Example: ```js const states = [ {name: 'Alabama', capital: 'Montgomery'} {name: 'Arizona', capital: 'Phoenix'} // ... ] for (const state of states) { console.log(state.capitol); } ``` This is a valid JavaScript programm which will log: ``` undefined undefined ``` ??? - Wie werden aber sehen, dass man das Output von fehlerhaftem JavaScript-Code durch eine Compiler-Option unterbinden kann. Compiler-Optionen steuern den Kompilierprozess und man kann z.B. sagen "generiere kein JavaScript, wenn TS Compiler irgendwelche Fehler entdeckt hat". - Frage: was ist falsch in diesem Code? --- # Relationship between TypeScript and JavaScript Let's compile the code with TypeScript. Even without adding type annotations, TypeScript type checker is able to spot the error and offer a helpful suggestion. ```ts const states = [ {name: 'Alabama', capital: 'Montgomery'} {name: 'Arizona', capital: 'Phoenix'} // ... ] for (const state of states) { console.log(state.capitol); // ~~~~ Property 'capitol' does not exist on type // '{ name: string; capital: string; }'. // Did you mean 'capital'? } ``` --- # Relationship between TypeScript and JavaScript TypeScript's type system models the runtime behavior of JavaScript. This may result in some suprises if you' re coming from a laguage with stricter runtime checks. Example: ```ts const x = 2 + '3'; // OK, type is string ``` The statement passes the type checker, even though they do produce runtime errors in many other languages. This does model the runtime behavior of JavaScript, where the the expression results in the string `"23"`. On the other hand, TypeScript compiler is stronger than the runtime behavior of JavaScript. Example: ```ts const a = null + 7; // Evaluates to 7 in JS // ~~~~ Operator '+' cannot be applied to types ... ``` ??? - Das Laufzeit-Verhalten von JavaScript kann euch überraschen, wenn ihr bis jetzt in den strikten Programmiersprachen, wie Java oder C# programmiert haben. --- # Relationship between TypeScript and JavaScript If you program type checks, could it still throw an error at runtime? ??? Quiz: Wenn ein TS program das Type Checking überstanden hat, kann es eine Exception zur Laufzeit werfen? Was denkt ihr? -- The answer is "yes". Here's an example: ```ts const names = ['Alice', 'Bob']; console.log(names[2].toUpperCase()); ``` When you run this, it throws ```ts TypeError: Cannot read property 'toUpperCase' of undefined ``` TypeScript assumed the array access would be within bounds, but it was not. This assumption led to an error at runtime. --- # Code generation TypeScript files use a __.ts__ (or __.tsx__) extension, rather than the __.js__ (or __.jsx__) extension of a JavaScript file.  __Target__ signifies which target of JavaScript should be emitted from the given TypeScript. ??? Der Compiler kompiliert / transpiliert den TS Code in JS Code. Dabei kann das Target via Compiler-Options angegeben werden. Nicht alle Browser unterstützen die modernen JavaScript Features. Mittels Target kann ich meine Browser-Platform auswählen, wo mein Program nachher läuft. Die wichtigsten Kompileroptionen und wo man sie angibt, werden wir noch kennenlernen. An dieser Stelle möchte ich aber dennoch ein Beispiel für die Target-Option geben. --- # Code generation __target: es5__ ```ts () => null ``` will become ```js function() {return null} ``` as ES5 doesn't have arrow functions. __target: es6__ ```ts () => null ``` will become ```js () => null ``` as ES6 has arrow functions. --- # Installation and Output You can install TypeScript via `npm` globally in the command line: ``` npm install -g typescript ``` Global installation means, the TypeScript compiler __tsc__ can be reached and used in any of your projects. Installed
Node.js
and npm are presupposed. Node.js is the JavaScript runtime environment. npm is the package manager. It is shipped with Node.js, but can be installed separately as well. After that, you can transpile one or more __.ts__ files into __.js__ files by typing: ``` tsc some.ts another.ts ``` This will result in two files, `some.js` and `another.js`. TypeScript can also be installed locally (below your project root) by typing ``` npm install --save-dev typescript ``` It writes the typescript into your `package.json`. ??? - Globale Installation heisst, der TS Compiler kann von jeder Stelle / jedem Verzeichniss in der Command Line angesprochen werden. - Lokalle Installation installiert den Compiler im Projekt selbst unter node_modules Verzeichnis. Zeigen IntelliJ Settings für TS in einem Projekt + node_modules. --- # Learning TypeScript Playground Hands-on learning is possible with the
TypeScript playground
which which compiles on-the-fly TypeScript code entered in a browser and shows it side by side with the generated JavaScript code.  ??? MS hat stellt einen TS Playground zur Verfügung, mit dessen Hilfe TS Code Snippets direkt ausprobiert werden können. Auf der linken Seite kann man die TS Snippets eingeben und auf der rechten Seite sieht man den generierten JS Code. Im Playground selbst kann folgende Einstellungen vornehmen: - TS Version auswählen - Fertige Beispiele auswählen. Classes auswählen. - Kompileroptionen einstellen. Den JS Code für Klassen mit den Targets ES5 und ES6 zeigen (Unterschied). - Code in der Browser Console laufen lassen (Run = Ctrl + Enter). Console mit F12 öffnen. --- # Primitive types TypeScript exposes the basic types, as well as a couple of extra types. Let's explore the type system with examples. __boolean__. The type is a primitive JavaScript boolean. ```ts let success: boolean = true; ``` __number__. The type is a primitive JavaScript number. ```ts let count: number = 20; ``` __string__. The type is a primitive JavaScript string. ```ts let message: string = "Hello world"; ``` --- # Primitive types __Array__. The type is an array of value. There are two equivalent notations. ```ts let ages: number[] = [31, 20, 65]; let ages: Array
= [31, 20, 65]; ``` __Tuple__. The type represents a heterogeneous array of values. Tuple enables storing multiple fields of different types. ```ts let x: [string, number]; x = ["age", 40]; // ok x = [40, "age"] ; // error ``` __object__. The type represents the non-primitive type, i.e. anything that is not `number`, `string`, `boolean`, `bigint`, `symbol`, `null`, or `undefined`. __Attention:__ There is `object` (lowercased) and `Object` (uppercased)! - `object` represents all non-primitive types. - `Object` describes functionality that is common to all JavaScript objects, e.g. the methods `toString()`, `hasOwnProperty()`, etc. ??? - Bei Arrays tendiere ich persönlich zu der 1. Schreibweise - Tuples erlauben Array Definitionen mit unterschiedlichen Typen einzelner Elementen --- # Primitive types Example of usage of `object`: The `Object.create()` method specifies the type `object | null` for its parameter: ```ts interface ObjectConstructor { // Creates an object that has the specified prototype or // that has null prototype. create(o: object | null): any; ... } ``` Examples: ```ts Object.create(proto); // OK Object.create(null); // OK -> creates "pure" object Object.create(undefined); // Error Object.create(1337); // Error Object.create(true); // Error Object.create("oops"); // Error ``` ??? Kennt ihr die Methode create()? Die Methode create des Konstruktors Object erzeugt ein neues gewöhnliches Objekt. Der Prototyp des erzeugten Objektes ist der Wert, welcher der Methode beim Aufruf als erstes Argument übergeben wurde. D.h. das erzeugte Objekt erbt alles von dem übergebenen Objekt (vom Prototype des übergebenen Objektes, alle Properties und Methoden). --- # Types void, null and undefined __void__. The type represents the absence of having any type. This type is normally used as the return type of functions. ```ts function doSomething(): void { // do something } ``` __undefined__ and __null__. These types represents `undefined` and `null` respectively. ```ts // undefined means, the value hasn't been initialized let u: undefined = undefined; // null means, the value is currently unavailable let n: null = null; ``` Per default, `null` and `undefined` are valid values of every type. That means, they can be assigned to any other type. ```ts let x: string = "foo"; x = null; // ok ``` ??? Wir werden später sehen, wie dieses Default-Verhalten mit Hilfe von Compiler-Optionen geändert werden kann. --- # Difference between any, unknown and never __any__. The type is anything. It is useful when you need to describe the type of variables that you do not know at the time of writing your application. You can assign a value of arbitrary type to a variable of type `any`. A value of type `any` in turn can be assigned to a variable of arbitrary type. ```ts let some: any = "some"; some = 10000; some = false; let success: boolean = some; let count: number = some; let message: string = some; ``` When to use `any` type in TypeScript? Due to the nature of JavaScript, in some cases providing accurate types isn't a simple task. Use the `any` when there no type definitions available for that particular piece of code you're working with. ??? any sollte nur in Ausnahmefällen benutzt werden, wenn der Type wirklich nicht bekannt / nicht aus dem existierenden Code abgeleitet werden kann. --- # Difference between any, unknown and never - `any` disables the type checker => there's no type safety with `any` type. Compare two code pieces: ```ts let age: number; age = 12; age += 1; // age = 13 ``` ```ts let age: any; age = '12'; age += 1; // no errors at runtime, but age is now "121" ``` - `any` lets you break contracts. When you write a function, you are specifying a contract for expected input and output. But with `any` you can break these contracts. E.g. ```ts function calculateAge(birthDate: Date): number { ... } let birthDate: any = '1990-01-19'; calculateAge(birthDate); // OK at compile time, // but can produce error at runtime ``` ??? Warum is any type mit Vorsicht zu geniessen? Any birgt viele Nachteile in sich. Nur zwei Beispiele. --- # Difference between any, unknown and never TypeScript 3.0 introduced a new `unknown` type which is the type-safe counterpart of the any type. The main difference between `unknown` and `any` is that `unknown` is much less permissive than `any`: we have to do some form of checking before performing most operations on values of type `unknown`. - Common part for `any` and `unknown`: just like all types are assignable to `any`, all types are assignable to `unknown`. ```ts let value: unknown; value = true; // OK value = 42; // OK value = "Hello World"; // OK value = []; // OK value = {}; // OK value = Math.random; // OK value = null; // OK value = undefined; // OK value = new TypeError(); // OK value = Symbol("type"); // OK ``` ??? - unknown ist nicht so tolerant oder freizügig als any und bringt mehr Typesicherheit ins Spiel. - unknown zwingt die Entwickler mehr Prüfungen hinsichtlich der Typen zu machen, bevor man einen Wert vom Typ unknown überhaupt verwendet. - unknown hat sowohl die Gemeinsamkeiten, als auch die Unterschiede zu any. --- # Difference between any, unknown and never - Different part between `any` and `unknown`: the `unknown` type is only assignable to the `any` type and the `unknown` type itself. ```ts let value: unknown; let value1: unknown = value; // OK let value2: any = value; // OK let value3: boolean = value; // Error let value4: number = value; // Error let value5: string = value; // Error let value6: object = value; // Error let value7: any[] = value; // Error let value8: Function = value; // Error // Furthermore, none of the operations // are considered type-correct anymore! // Examples: value.trim(); // Error value(); // Error value[0]; // Error ``` --- # Difference between any, unknown and never __Main advantage of unknown__ This is useful for APIs that want to signal "this can be any value, so you must perform some type of checking before you use it". This forces users to safely introspect returned values. ```ts // Type assertion to rescue const value: unknown = "Hello World"; (value as string).toUpperCase(); // "HELLO WORLD" ``` When to use: - when the client gets a response from backend and data type is unknown. - when the client reads the data from browser's persistence storage. - ... ??? - Um mit `unknown` vernüftig ohne Compiler-Fehler zu arbeiten, ist der Entwickler dazu gezwungen, über den Type nachzudenken und type assertions to verwenden (werden weiter kennenlernen). - In TS playground probieren: ```ts const value: unknown = "Hello World"; console.log(value.toUpperCase()); console.log((value as string).toUpperCase()); ``` - Wann sollte unknown benutzt werden? - Z.B. wenn man die Daten vom Server in HTTP-Response bekommt und der Typ der Daten nicht bekannt ist (unknown zu benutzen ist hier sicherer als any) - Wenn man die Daten aus dem Browser Persistence-Storage liest. Im einfachsten Fall liest man die Daten mit localeStorage (blocking API: getItem, setItem). --- # Difference between any, unknown and never __never__. The `never` type represents the type of values that never occur. For instance, `never` is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns. ```ts function error(message: string): never { throw new Error(message); } ``` ```ts function infiniteLoop(): never { while (true) { } } ``` - The `never` type is assignable to every type. - However, no type is assignable to `never` (except never itself). Even `any` isn't assignable to never. --- # Difference between any, unknown and never __Summary__ Types `any`, `unknown` and `never` at a glance.  ??? Wie gesagt, any hebelt den Type-Checker komplett aus und bringt die meiste Freiheit hier. Aber any bringt auch die meiste Typunsicherheit und die Laufzeitfehler mit. Programmieren mit any in TS ist mit dem Programmieren in pure JS gleichgestellt. Alle Vorteile einer typisierten Sprache gehen verloren! --- # Enums Enums allow us to define a set of named constants. There are Numeric, String and Heterogeneous enums. __Numeric enums__ Enum members have numeric values associated with them (started with 0): ```ts enum Color { Red, Green, Blue } let color = Color.Red; // color has value 0 ``` __String enums__ ```ts enum Direction { Up = "UP", Down = "DOWN" } ``` ??? - Vorteil von String enums liegt auf der Hand. Die Werte sind aussagekräftiger als bei numerischen enums, was beim Debugging sehr hilft. - Heterogener enum vereint Numeric und String enums (man kann beide mischen). TS empfielt diese Enums zu vermeiden; es ist nicht klar, wo sie überhaupt nützlich sind. - JS Code von enum Color in Playground zeigen. Zeigen versteckte Features: ``` console.log(Color["Blue"]); console.log(Color[2]); ``` --- # Interfaces An interface is a way to take a particular structure / shape and give it a name so that we can reference it later as a type. It defines a contract within our code. Interfaces begin with the keyword `interface`. ```ts interface Person { name: string; children?: number; isMarried(): boolean; (): void; } ``` The specified interface `Person` has the following: - The `name` property of type `string`. - The optional property `children` of type number. Optional properties are denoted by a question mark and can be omitted. - The `isMarried` method that returns a `boolean` value. - Anonymous (unnamed) method that returns nothing. ??? Interfaces können von einander erben. Dafür gibt es das Schlüsselwort extends. Bsp. in Playgrond: ```ts interface Employee extends Person { salary: number; } ``` --- # Interfaces TypeScript allows you to use the syntax `[index: type]` to specify a `string` or `number` type based collection of key/value pairs. Interfaces perfectly fit such data structures. For example: ```ts interface Dictionary { [index: number]: string; } ``` ```ts let dict: Dictionary = { 0: 'zero', 1: 'one', 2: 'two' }; ``` --- # Interfaces __An interface is only used by TypeScript compiler at compile time, and is then removed.__ Interfaces don't end up in the final JavaScript output. General, no types appear in the output. You can see that in the TypeScript Playground.  ??? Ein Interface ist nur zur Kompilierzeit sichtbar und taucht nirgends in dem Output. Generell, alle TS Typen verschwinden im kompilierten JS Code. Bei Enums haben wir dies bereits gesehen. Klassen, die wir auf der nächsten Folie kennenlernen, bleiben im Output, weil sie im EcmaScript-Standard schon lange enthalten sind (seit ES 2015). --- # Classes Beside interfaces, there are __classes__ that describe objects. A class acts as a template for instantiating specific objects. The syntax for TypeScript's classes is almost identical to that of native classes in ECMAScript 2015 with some handy additions. ```ts class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } // usage let greeter = new Greeter("world"); ``` ??? Wir haben eine Klasse `Greeter` deklariert, mit der Property `greeting`, einem Constructor und der Methode `greet()` --- # Classes In TypeScript we can extend a class using __extends__ keyword. ```ts class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } class Employee extends Person { salary: number; constructor(name: string, age: number, salary: number) { super(name, age); this.salary = salary; } } // usage let employee = new Employee("Max", 35, 3000); ``` ??? Der Constuctor von der Superklasse kann mit dem Schlüsselwort super aufgerufen werden --- # Classes A class can implement an interface using the __implements__ keyword. If a class implements some interface, it must adopt all properties from this interface; otherwise, you will get an error about missing properties. ```ts interface Animal { name: string; } class Dog implements Animal { name: string; // do specific things } ``` --- # Public, private, and protected modifiers In TypeScript, you can use `public`, `private` and `protected` access modifiers: ```ts class Dog { private name: string; // can only be accessed within this class public owner: string = "Max"; constructor(name: string) { this.name = name; } protected sayBark() { ... } } let dog = new Dog("Sam"); dog.sayBark(); // compiler error because method 'sayBark' // is protected and only accessible within // the class 'Dog' and its subclasses. ``` Members with omitted modifiers are `public` by default. --- # Public, private, and protected modifiers It is possible to declare a constructor parameter with a modifier. As result, a member will be created and initialized in one place. Example: ```ts class Dog { constructor(private name: string) { } public printName(): void { // you can now access the property name by this.name console.log(this.name); } } ``` ❗ _This shortened syntax is often used in Angular when we inject services into components. Angular's services are normally declared in the component's constructor with the private modifier._ --- # Keywords static and abstract If a property or method is declared with the `static` keyword, there is no need to create an instance to access them. ```ts class Circle { static pi: number = 3.14; static calculateArea(radius:number) { return this.pi * radius * radius; } } ``` Usage: ```ts Circle.pi; // returns 3.14 Circle.calculateArea(5); // returns 78.5 ``` --- # Keywords static and abstract A class can be abstract, that means, it may not be instantiated directly. Abstract classes begin with the keyword `abstract`. The `abstract` keyword is used to define abstract classes as well as abstract methods within an abstract class. ```ts abstract class Animal { abstract makeSound(): void; move(): void { console.log("roaming the earth..."); } } ``` __Note:__ Methods within an abstract class that are marked as abstract do not contain an implementation and must be implemented in derived classes. --- # Interfaces extending classes In TypeScript, interfaces can extend classes. When an interface type extends a class type it inherits the members of the class but not their implementations. This means that when you create an interface that extends a class with private or protected members, that interface type can only be implemented by that class or a subclass of it. ```ts class Control { private state: any; } interface SelectableControl extends Control { select(): void; } class Button extends Control implements SelectableControl { select() { } } // Error: Property 'state' is missing in type 'Image'. class Image implements SelectableControl { private state: any; select() { } } ``` ??? - Auch private and protected Members werden vererbt. - Das ist nützlich, wenn man explizit ausdrücken will, dass der Code nur mit Subklassen funktioniert, welche bestimmte Properties aufweisen. --- # Getters / setters accessors Getters and setters (also known as accessors) were introduced to JavaScript when ECMAScript 5 (2009) was released. They provide an alternative way to access the properties of an object. ```ts class Person { private _age: number; public set age(age) { if (age < 0 || age > 200) { throw new Error('Invalid arguement age'); } this._age = age; } public get age() { return this._age; } } const person: Person = new Person(); person.age = -10; // error at runtime ``` ??? - The Underscore vor dem age wird öfters für private Felder benutzt. - Man kann in setters eine Validierung, wie im Beispiel machen. - Was kann man in getters machen? In getters kann man z.B. einen neuen Rückgabewert aus mehreren Properties zusammenstellen. Bsp.: denkbar wäre eine Klasse mit den Properties "firstName" und "surname", und einem getter "fullName". In Getter werden "fullName" und "surname" konkateniert. --- # Modules ECMAScript 2015 has introduced built-in modules. The features of modules are as follows: - Each module is defined in its own file. - Functions or variables defined in a module are not visible outside unless you explicitly export them. - You can place the __export__ keyword in front of any variable, function, or class declaration to export it from the module. - You can use the __import__ keyword to consume the exported variable, function, or class declaration. - Modules are singletons. Only a single instance of a module exists, even if it was imported multiple times. --- # Modules Some exporting possibilities are listed here: ```ts // export data export let color: string = "red"; // export function export function sum(num1: number, num2: number) { return num1 + num1; } // export class export class Rectangle { constructor(private length: number, private width: number) { } } ``` --- # Modules You can declare a variable, function, or class and export it later. You can also use the `as` keyword to rename exports. A new name is the name used for importing. ```ts class Rectangle { constructor(private height: number, private width: number) { } } export {Rectangle as rect}; ``` Once you have a module with exports, you can access its functionality in another module using the `import` keyword: ```ts import {sum} from "./lib.js"; import {Rect, Circle} from "./lib.js"; let sum = sum(1, 2); let rect = new Rect(10, 20); ``` ??? Exports / imports an einem Beispiel-Projekt zeigen und sagen: Module werden automatisch durch die Bundlers, wie beispielsweise Webpack (Rollup, Parsel), zusammengebündelt. Bundlers verpacken JavaScript modules in einzelne JavaScript files, welche in Browser ausgeführt werden können. --- # Modules There is a special case that allows you to import the entire module as a single object. All exported variables, functions, and classes are available on that object as properties: ```ts import * as lib from "./lib.js"; let sum = lib.sum(1, 2); ``` Imports can be renamed with the `as` keyword and used under the new name: ```ts import {sum as add} from "./lib.js"; let sum = add(1, 2); ``` --- # Meet TypeScript compiler options TypeScript can feel like a very different language depending on how it is configured. Compiler settings define files and libraries to be included in the compilation, output structure, module code generation, and so on. They can be set __1.__ Via the command line ``` tsc --noImplicitAny program.ts ``` __2.__ Via the configuration file _tsconfig.json_. Example: ``` "compilerOptions": { "target": "es5", "module": "es2015", "outDir": "dist", "lib": ["es2015", "dom"] } ``` You should prefer the configuration file. It ensures that your coworkers and tools all know exactly how you plan to use TypeScript. --- # Meet TypeScript compiler options Officially documentation: https://www.typescriptlang.org/docs/handbook/compiler-options.html Example from a real project: ``` { "compileOnSave": false, "compilerOptions": { "downlevelIteration": true, "importHelpers": true, "outDir": "./dist/out-tsc", "baseUrl": "src", "sourceMap": true, "declaration": false, "module": "esnext", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es2018", "typeRoots": ["node_modules/@types"], "lib": ["es2018", "dom"] } } ``` --- # Meet TypeScript compiler options. noImplicitAny. To use TypeScript effectively, you should understand the most important compiler options: `noImplicitAny` and `strictNullChecks`. `noImplicitAny` controls whether variables must have known types. This code is valid when `noImplicitAny` is off: ```ts function add(a, b) { return a + b; } ``` TypeScript inferred `any` type (check in TypeScript Playground!). These are called _implicit anys_ because you never wrote the word "any". __Attention:__ The `any` type effectively disables the type checker! ??? - In TypeScript Playground die Mouse über die Parameter bewegen. Man sieht, dass TypeScript vom Type any ausgeht. Man nennt das Type Inferenz. - Explizite Type sind für Anfänger oder Projekt-Einsteiger sehr hilfreich. Damit verstehen sie den neuen Code viel besser. --- # Meet TypeScript compiler options. noImplicitAny. The code becomes an error if you set the `noImplicitAny` option: ```ts function add(a, b) { // ~~~ Parameter 'a' implicitly has an 'any' type // ~~~ Parameter 'b' implicitly has an 'any' type return a + b; } ``` These errors can be fixed by explicitly writing type declarations. ```ts function add(a: number, b: number): number { return a + b; } ``` --- # Meet TypeScript compiler options. strictNullChecks. We have seen the default behavior when setting nullable types `null` and `undefined`. For example: ```ts let x: number; x = 1; // ok x = undefined; // ok ``` It is not always desired. TypeScript offers a possibility to change this default behavior by setting the compiler options `strictNullChecks` to `true`. With this option set, you have to include these types explicitly using a union type (explained later on), otherwise, you will get an error. Example: ```ts let x: string = "foo"; x = null; // error let y: string | null = "foo"; y = null; // ok ``` __Benefit:__ This helps avoid many common errors! --- # Structural typing Strictly-typed programming languages can be either structurally or nominally typed. TypeScript is a structurally typed language - and this is a key difference to other strictly-typed languages such as C# or Java, that are nominally typed. This means that types are identified by their _shape_ rather than _name_ or _alias_. In nominally-typed languages the following code would be an error because the `Person` class does not explicitly describe itself as being an implementer of the `Named` interface. ```ts interface Named { name: string; } class Person { name: string; } let p: Named; // Error in nominally-typed languages, // but OK in structurally typed ones p = new Person(); ``` ??? Die Struktur, genannt als Shape, beider Objekten ist identisch. Beide haben die Eigenschaft "name". Deshalb klappt's auch die Zuweisung. --- # Structural typing Another example: ```ts type Person = { name: string; weight: number; } type Pet = { name: string; weight: number; } const man: Person = { name: "Oleg", weight: 88.4 }; const dog: Pet = { name: "Urmel", weight: 5.7 }; const sayName = (person: Person) => console.log(person.name); sayName(man); // Output: Oleg sayName(dog); // Output: Urmel ``` `sayName's` only condition is that whatever is passed to it has a property called `name`. This is
Duck Typing
, and follows the style you'd use to write typical JavaScript or Python code. ??? - Die Funktion "sayName" erwartet zwar eine Person, aber die Shape von dem Typ "Pet" ist die gleiche, wie bei Person (gleiche Eigenschaften). Deshalb gibt es keine Fehler vom TS Compiler. - TS nachahmt hier JS --- # Similarities and differences between type and interface If you want to define a named type in TypeScript, you have two options. You can use a __type__ (it doesn't create a new type, and is therefore called a _type alias_). ```ts type State = { name: string; capital: string; } ``` or an __interface__ ```ts interface State = { name: string; capital: string; } ``` Which should you use, `type` or `interface`? It depends... ??? Type alias beginnt mit dem Schlüsselwort "type". --- # Similarities between type and interface Extra properties beyound defintion lead to the same error. ```ts const bern: State = { name: 'Bern', capital: 'Bern' population: 1035000 // ~~~ Type '{name: string; capital: string; population: number;}' // is not assignable to type 'State'. Object literal may only // specify known properties, and 'population' does not exist // in type 'State'. } ``` You can use an _index signature_ with both. ```ts type TDict = { [key: string]: string }; interface IDict { [key: string]: string } ``` --- # Similarities between type and interface You can also define function types with either. ```ts type TFn = (x: number) => string; interface IFn { (x: number): string } ``` For both types, Generics are possible. Beispiel mit recursion (since TS 3.7): ```ts type TItem
= { value: T; reference: TItem
; } interface IItem
{ value: T; reference: IItem
; } ``` ??? Bei function types sieht die Definition mit type natürlicher aus. Beachtet bitte, wie der Rückgabetyp definiert ist. Standardmässig folgt der Rückgabetyp bei der Funktion-Definition einem Pfeil. --- # Similarities between type and interface An `interface` can extend a `type`, and a `type` can extend an `interface`. ```ts interface IStateWithPopulation extends TState { population: number; } type TStateWithPopulation = IState & { population: number; }; ``` __Exclusion__: An `interface` cannot extend a complex type like a union type. A class can implement either an `interface` or a similar `type`. ```ts class StateImpl implements TState { name: string = ''; capital: string = ''; } class StateImpl implements IState { name: string = ''; capital: string = ''; } ``` --- # Differences between type and interface Union types or extending union types cannot be expressed with `interface`. ```ts type Input = { ... }; type Output = { ... }; type InputOutput = Input | Output; ``` Tuples and array types can be more easily expressed with `type`. ```ts type Pair = [number, number]; type StringList = string[]; type NamedNums = [string, ...number[]]; ``` --- # Differences between type and interface You can merge interfaces but not types. This feature is called __"declaration merging"__. "Declaration merging" is when the TypeScript complier merges two or more types into one declaration provided the same name. TypeScript allows merging between multiple types such as `interface` with `interface`, `enum` with `enum`, `namespace` with `namespace`, etc. Example: ```ts interface Person { name: string; } interface Person { age: number; height: number; } class Employee implements Person { name = "Max" age = 36; height = 190 } const employee = new Employee(); console.log(employee) // {name: "Max", age: 36, height: 190} ``` ??? - Nur Declaration merging von Klassen ist nicht erlaubt. Fast die einzige Ausnahme. - Weil alle Interfaces mit dem gleichen Namen Person deklaririert sind, werden sie in eine TS Definition gemerged. Employee hat dann alle Eigenschaften einer Person. Das Interface Person kann natürlich in unterschiedlichen Dateien deklariert werden. Man kann mehr sagen: nur so macht es überhaupt Sinn. --- # Differences between type and interface Why use declaration merging and where does it shine? - You can extend declarations of third-party libraries that you import into your project. Example:
OffscreenCanvas by "Definitely Typed"
- the repository for high quality TypeScript type definitions. - You can extend declarations of generated TypeScipt definitions, which are usually coming from backend. Example: generated TypeScript code from plain Java objects by a Maven plugin such as
typescript-generator
. - TypeScript uses merging to get different types for the different versions of JavaScript's standrad libraries. Example: the `Array` interface. It is defined in _lib.es5.d.ts_ file. By default this is all you get. But if you add _ES2015_ to the _lib_ entry of your _tsconfig.json_, TypeScript will also include _lib.es2015.d.ts_. This includes another `Array` interface with additional methods like _find_ that were added in _ES2015_. They get added to the other `Array` interface via merging. ??? - Gibt es andere Programmiersprachen mit dem vergleichsweise ähnlichen Feature? Ich weiss es nicht. - Definitely Typed hat fast alle denkbaren TS type definitions für third-party JavaScript libs. Sie können in jeweiligen TS Projekten installiert werden und bringen Typen und Autocompletion in diese Projekte, wenn diese Projekte third-party Libraries verwenden. OffscreenCanvas ist Standard Web API, ist in der Spec. verankert und erlaubt sogenanntes "Offscreen Rendering", d.h. Rendering auf einem Canvas ohne DOM (entkopelt vom HTML DOM). D.h. das Rendering auf einem OffscreenCanvas kann in WebWorkers ausgelagert werden. Das Main-Thread bleibt damit entlastet und kann nur für User-Interaktionen benutzt werden. Zeitintensives Rendering findet in WebWorkers statt. - Declaration merging im realen Projekt vorstellen. Maven generiert TS Deklarationen aus Java-Code (zeigen). Manchmal gibt es aber Eigenschaften, welche im Backend keinen Sinn machen. Z.B. Selection macht nur im Frontend einen Sinn und muss nur dort anhand einer zusätzlichen Property bei einem generierten Interface definiert werden. --- # Type assertions Sometimes, you would like to tell compiler that you know the type better than it does and it should trust you. Type assertions are a way to tell the compiler _"trust me, I know what I'm doing."_ For instance, imagine a situation where you receive data over HTTP and know exactly the structure of the received data. The compiler doesn't know such structure of course. In this case, you want to turn off the type checking when assigning the data to a variable. It is possible with so called __type assertions__. Type assertion is like a type cast in other languages, but without the checking of data. It has no runtime impact, and is used purely by the compiler. You can do that either with _angle bracket_ or the _as_ syntax. ```ts let element =
document.getElementById('canvas'); let element = document.getElementById('canvas') as HTMLCanvasElement; ``` ```ts let x: any = "hi there"; let s = (
x).substring(0,3); // or alternative let s = (x as string).substring(0,3); ``` --- # Type inference There are several places where type inference is used to provide type information when there is no explicit type annotation. __Tip:__ Avoid cluttering your code with inferable types. The explicit type information is often redundant. Writing it just addes noise. Don't write: ```ts let x: number = 12; ``` Instead, just write: ```ts let x = 12; ``` Simple example of type inference: ```ts let a = "some text"; let b = 123; a = b; // Compiler error: // Type 'number' is not assignable to type 'string' ``` ??? - TS ist in der Lage, die Typen aus der Context-Information abzuleiten. - Zeigen den Typ in TS Playground mit Mouseover. --- # Type inference As you see, the type can be omitted if the compiler is able to infer it. TypeScript improves the type inference continuously. It tries to guess a best common type when elements of several types are present in an array. ```ts let x = [0, 'a', null]; ``` inferred as ```ts (number | string | null)[] ``` Next example with complex objects. ```ts class Sheepdog extends Dog { ... } let animal = [new Dog(), new Sheepdog()]; ``` The type of the variable `animal` is `Dog[]`. --- # Type inference Quiz: What do you think, what is the type of `animal` here? ```ts class Fish { kind: string; } let animal = [new Dog(), new Sheepdog(), new Fish()]; ``` -- The best common type of the next array is `(Dog | Fish)[]` because the class `Fish` doesn't extend to any other class. The return type of a function is also inferred by the returning value. ```ts function sum(a: number, b: number) { return a + b; } let total: number = sum(10,20); // OK let str: string = sum(10,20); // Compiler error ``` --- # Type inference Type inference also works when the type of an expression is implied by its location (this is known as "contextual typing"). Example: ```ts window.onmousedown = function(mouseEvent) { console.log(mouseEvent.button); // <- OK console.log(mouseEvent.kangaroo); // <- Error! }; ``` The TypeScript type checker used the type of the `Window.onmousedown` function to infer the type of the function on the right hand side. As result, it was able to infer the type of the `mouseEvent` parameter, which does contain a `button` property, but not a `kangaroo` property. TypeScript may infer something more precise they what you expect. ```ts const axis1: string = 'x'; // Type is string const axis2 = 'y'; // Type is "y" ``` "y" is more precise type for the `axis2` variable. --- # Optional and required properties We can specify that a property is optional with a question mark after the name: ```ts interface InterfaceWithOptional { foo?: number; } ``` `foo?: number` means that the property `foo` may not exist at all on instances of `InterfaceWithOptional`. This is different to `undefined` where the property itself should exist, but the value can be `undefined`. ```ts interface InterfaceWithUndefined { foo: number | undefined; } ``` --- # Optional and required properties Property without a question mark is required. We can make all optional properties as required. This transformation can be done by the utility type `Required
`. Example: ```ts interface Props { a?: number; b?: string; }; const obj1: Props = { a: 5 }; // OK const obj2: Required
= { a: 5 }; // ~~~ Error: property 'b' missing ``` --- # Readonly properties Properties marked with `readonly` can only be assigned to during initialization or from within a constructor of the same class. All other assignments are disallowed. ```ts class Circle { readonly radius: number; constructor(radius: number) { this.radius = radius; } get area() { return Math.PI * this.radius ** 2; } } ``` ```ts const circle = new Circle(1); circle.radius = 42; // ~~~ Cannot assign to 'radius' // because it is a read-only property. ``` --- # Readonly properties Arrays can be done readonly by using the `ReadonlyArray
` type. Example: ```ts const values: ReadonlyArray
= ["a", "b", "c"]; ``` This means that we don't intend for this array to be mutated. TypeScript's type checker will produce an error if we try to write to the array or call mutating array methods. ```ts values[0] = "x"; // Type error values.push("x"); // Type error values.pop(); // Type error ``` ❗ The `readonly` modifier is part of TypeScript's type system. It's only used by the compiler to check for illegal property assignments. Once the TypeScript code has been compiled to JavaScript, all notions of readonly are gone. ❗ Because `readonly` is only a compile-time artifact, there's no protection against property assignments at runtime! --- # Readonly properties `readonly` works shallow! Only the first level in a nested complex object is readonly. ```ts interface Outer { inner: { x: number; } } ``` ```ts const o: Readonly
= { inner: { x: 0 } }; o.inner = { x: 1 }; // ~~~ Cannot assign to 'inner' // because it is a read-only property. o.inner.x = 1; // OK ``` A deep readonly type is suggested:
DeepReadonly type
. --- # Const assertions in literal expressions Good news: literal expressions can be made deep readonly. A __const assertion__ is a special type assertion that uses the `const` keyword. Using a const assertion on a literal expression make all properties (also nested) of the object literal readonly. Try the last example: ```ts const outer = { inner: { x: 0 } } as const; ``` ```ts outer.inner = { x: 1 }; // ~~~ Cannot assign to 'inner' // because it is a read-only property. outer.inner.x = 1; // ~~~ Cannot assign to 'x' // because it is a read-only property. ``` ??? Man kann "as const" mit Typen probieren, readonly properties werden damit aber nicht funktionieren. In Playground probieren: ```ts interface Outer { inner: { x: number; } } const outer: Outer = { inner: { x: 0 } } as const; outer.inner = { x: 1 }; outer.inner.x = 1; ``` --- # Excess property checks TypeScript has a feature called "Excess property checks". The aim of this feature is a typos detection when a type isn't expecting a specific property. Assume, we have an interface `Circle` and a function `drawCircle`. ```ts interface Circle { color?: string; radius?: number; } function drawCircle(rect: Circle) { // ... } ``` Now, if we call this function with an object literal having a typo _colour_ instead of the property name _color_, we will see ```ts drawCircle({ colour: "red", radius: 5 }); // ~~~ Argument of type '{ colour: string; radius: number; }' // is not assignable to parameter of type 'Circle'. // Object literal may only specify known properties, // but 'colour' does not exist in type 'Circle'. // Did you mean to write 'color'? ``` --- # Excess property checks TypeScript assumes here, it is probably a bug in this code. You have probably a typo when passing an object literal with an unknown property __although__ all properties were optional! How to fix that? __1.__ Use a type assertion ```ts drawRectangle({ radius: 100, opacity: 0.5 } as Circle); ``` __2.__ Add a string index signature if you're sure that the object can have some extra properties. ```ts interface Circle { color?: string; radius?: number; [propName: string]: any; } ``` ??? Bei index signature sagen wir: Im Interface Circle können wir eine beliebige Anzahl von Properties haben und solange sie nicht "color" und "radius" sind, ist der Type dieser Properties uns egal. In Playground prüfen (neues Interface Circle dort einsetzen -> der Compiler Fehler soll weg sein). --- # Excess property checks __3.__ Assign the object literal to another variable (recommended way). ```ts const rect = { colour: "red", radius: 5 }; drawRectangle(rect); ``` The above workaround will work as long as you have a common property between the `rect` variable and `Circle` interface. In this example, it was the property `radius`. It will however, fail if the variable does not have any common object property. For example: ```ts const rect = { colour: "red" }; drawRectangle(rect); // ~~~ Type '{ colour: string; }' has // no properties in common with type 'Circle' ``` ??? Hier wird der "Excess property check" ausgehebelt (obwohl der Schreibfehler mit "colour" immer noch besteht). Es funktioniert aber ähnlich, wie wir eine Klassen-Instanz an eine Methode passen, wo als Parameter eine Super-Klasse oder ein Interface mit weniger Properties erwartet wird. --- # Functions Parameters and return values in the function signature can be typed too. Types protects you against JavaScript errors during function execution because the compiler warns you punctually at build time when the wrong types are used: ```ts function add(x: number, y: number): number { return x + y; } ``` __Function type__ is a way to declare the type of a function. To explicitly declare a function type, you should use - the keywords `let` or `const` - a variable name - a colon - a parameter list - a fat arrow `=>` - and the function's return type ```ts let fetchName: (division: Division, customer: Customer) => string; ``` ??? - Wir haben bereits mehrere Beispiele von Funktionen gesehen, aber noch nicht genau die Funktionen in TS besprochen. - fat arrow (der fette Pfeil) muss vor dem Typ des Rückgabewertes in Funktion-Deklarationen stehen. In den Funktion-Definitionen (also bei der Implementierung einer Funktion) wird ein Doppelpunkt verwendet. - Die Funktion fetchName ist hier als folgt deklariert: sie hat zwei Parameter vom Typ Division und Customer entsprechend und einen Rückgabewert vom Typ string. --- # Functions Now, you must provide an implementation of this declaration: ```ts fetchName = function (division: Division, customer: Customer): string { // do something } ``` This technique is especially useful for callbacks. Imagine a filter function which filters arrays by some criterion. An exact criterion can be encapsulated in the passed in callback function that acts as predicate: ```ts function filter(arr: number[], callback: (item: number) => boolean): number[] { let result: number[] = []; for (let i = 0; i < arr.length; i++) { if (callback(arr[i])) { result.push(arr[i]); } } return result; } ``` --- # Functions A possible function call with a specific callback could appear as follows: ```ts let result = filter([1, 2, 3, 4], (item: number) => item > 3); ``` In TypeScript, every function parameter is assumed to be required. There are two ways to mark a parameter as optional. -- __1__. Use a question mark after the parameter name: ```ts function doSomething(param1: string, param2?: string) { ... } ``` __2__. Use the parameter's default value (ECMAScript 2015 feature), which gets applied when no value is provided: ```ts function doSomething(param1: string, param2 = "some value") { ... } ``` Now, you are able to call this function with just one value. ```ts doSomething("just one value"); ``` ??? - Übergabewerte für optionale Parameter können bei Funktionsaufruf ausgelassen werden. - Wer kennt diese 2 Optionen, um einen Parameter einer Funktion optional zu machen? Mindestens eine Option sollte euch bekannt sein, weil sie im ECMAScript Standard verankert ist und kann somit auch in JS verwendet werden. Die andere Möglichkeit ist ein reines TS-Feature, und das haben wir schon übrigens kennengelernt. --- # Functions Yet another feature of ECMAScript: In ECMAScript 2015 and TypeScript __rest__ parameters and __spread__ operator provide a convenient to work with variable number of arguments. ```ts function varArgs(...args: number[]) { console.log(args.length); console.log(Math.max(...args)); } ``` Check in TypeScript Playground: ```ts varArgs(1, 5, 3, 0); ``` This feature also works with arrow functions (another ECMAScript 2015 feature). ```ts let varArgs = (...args: any[]) => { console.log(...args); } ``` ??? - Mit der Rest Parameter Syntax kann man beliebig viele Parameter als Array empfangen. - Der Spread Operator erlaubt ein Array von Elementen (oder ein beliebiges iterierbares Objekt von Elementen) in einzelne Elemente zu "entpacken" Mit der Spread-Syntax kann ein einzelner Ausdruck dort expandiert werden, wo mehrere Argumente oder Elemente oder Variablen erwartet werden. --- # Function overloading TypeScript provides the concept of function overloading. You can have multiple functions with the same name but different parameter types and return type. However, __the number of parameters should be the same__. ```ts function reverse(string: string): string; function reverse
(array: T[]): T[]; function reverse
(stringOrArray: string | T[]): string | T[] { return typeof stringOrArray === "string" ? stringOrArray .split("") .reverse() .join("") : stringOrArray.slice().reverse(); } ``` The first two lines of the above example list the valid overloads of the reverse function. They are visible for the callers (check autocompletion!). On the third line, we specify the generic "internal" signature, which must be compatible with all specified overloads. The implementation itself, which is typed using union types, doesn't show up in the autocompletion. ??? Autocompletion in TS Playground prüfen --- # _this_ in functions and callbacks In JavaScript, __this__ is a variable that's set when a function is called. You can run into errors with `this` in callbacks, when you pass functions to a library that will later call them. Because the library that calls your callback will call it like a normal function, `this` will be `undefined`. TypeScript allows to explicitly specify the type of `this` in functions. This way we can tell the compiler that what the intended type of `this` will be during execution time. Assume, a library author annotates the callback type with `this`. ```ts interface UIElement { addClickListener(onclick: (this: void, e: Event) => void): void; } ``` __this: void__ means that `addClickListener` expects `onclick` to be a function that does not require a `this` type. This is a __contract__ of `onclick` for all consumers of this library. __Tip__: To enforce that `this` types are always explicitly annotated in functions use the compiler option __noImplicitThis__. ??? Ihr könnet eine explizite Angabe von this in den Funktionen mit der Compiler Option "noImplicitThis" erzwingen. In TS Playground zeigen. --- # _this_ in functions and callbacks Assume, a consumer of this library defines a handler class with an onclick handler (acts as callback which gets called when an UI element is clicked). ```ts class Handler { info: string; onClick(this: Handler, e: Event) { this.info = e.message } } let handler = new Handler(); uiElement.addClickListener(handler.onClick); // error at compile time ``` `this` in the `onClick` is to be expected of type `Handler` => error at compile time. Without __this: Handler__ in the `onClick` there is an error at runtime. To fix the error, change the type of `this`: ```ts class Handler { info: string; onClick(this: void, e: Event) { console.log('clicked!'); } } ``` ??? this kann jetzt nicht innerhalb der Methode onClick benutzt werden. Das entspricht der Callback-Deklaration in addClickListener. --- # Generics In TypeScript, you can define generic functions, interfaces, and classes like in other programming languages. A generic function has type parameters listed in angle brackets: ```ts function reverseAndMerge
(arr1: T[], arr2: T[]): T[] { return arr1.reverse().concat(arr2.reverse()); } let arr1: number[] = [1, 2, 3]; let arr2: number[] = [4, 5, 6]; let arr = reverseAndMerge(arr1, arr2); ``` Such generic functions can be defined with generic interfaces as well. The function signature for `reverseAndMerge` is compatible with the following generic interface: ```ts interface GenericArrayFn
{ (arr1: T[], arr2: T[]): T[]; } let arr: GenericArrayFn
= reverseAndMerge; ``` --- # Generics Note that the generic type parameter list in angle brackets follows the name of the function and interface. This is also true for classes: ```ts class GenericValue
{ constructor(private value: T) { } increment: (x: T) => T; decrement: (x: T) => T; } let genericValue = new GenericValue
(5); genericValue.increment = function (x) {return ++x;}; genericValue.decrement = function (x) {return --x;}; ``` When creating factories in TypeScript using generics, it is necessary to refer to class types by their constructor functions. ```ts function create
(c: { new(): T; }): T { return new c(); } ``` ??? new() gekennzeichnet einen Constructor, welcher in diesem Fall eine Klassen-Instanz von einem generischen Typ T erzeugt. In Playground zeigen: ```ts function create
(c: { new(): T; }): T { return new c(); } class Rectangle { } let rect = create
(Rectangle); ``` --- # Undestand type widening and narrowing At runtime every variable has a single value. But at static analysis time, when TypeScript is checking your code, a variable has a set of _possible_ values, depending on type. When you initialize a variable with a constant but don't provide a type, the type checker needs to decide on one. In other words, it needs to decide on a set of possible values from a single value that you specified. In TypeScript, this process is known as __widening__. Example: ```ts let mixed = ['x', 1]; ``` What should the type of `mixed` be? Here are a few possibilities: ``` ('x' | 1)[] ['x', 1] [string, number] (string, number)[] [any, any], any[] ... ``` Without more context, TypeScript has no way to know which one is "right". It has to quess. The decision in this case: `(string|number)[]`. ??? - Das ist ein komplexes Thema, so dass wir nicht alles in Details im Rahmen dieses Vortrages beleuchten können. Unser Ziel ist hier ein wenig zu verstehen, wie der TS Compiler arbeitet, wenn er die Typen aus dem Code ableitet. - TS muss entscheiden, welche möglichen Werte eine Variable annehmen kann, wenn kein Typ explizit definiert wurde. Das hat gewisse Überschneidung mit dem Thema Type Inference (wurde besprochen). Aber hier wollen, wie gesagt, verstehen, wie der TS Compliler die Entscheidungen bzgl. Typen trifft. --- # Undestand type widening and narrowing Suppose, you're writing a library to work with vectors. ```ts interface Vector3 { x: number; y: number; z: number; } function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') { return vector[axis]; } ``` When we try to use it as follows, TypeScript shows an error (but there are no erros in JavaScript, the code runs fine). ```ts let x = 'x'; let vec = { x: 10, y: 20, z: 30 }; getComponent(vec, x); // ~~~ Argument of type 'string' is not assignable // to parameter of type '"x" | "y" | "z"' ``` --- # Undestand type widening and narrowing The type of `x` is inferred as `string` because TypeScript chooses to allow code like this: ```ts let x = 'x'; x = 'Some string'; x = 'Hello world'; ``` TypeScript attempts to strike a balance between specificity and flexibility. There are a few ways to control the process of widening. One is __const__. If you declare a variable with `const` instead of `let`, it gets a narrower type. In the example, the error will be gone. ```ts const x = 'x'; let vec = { x: 10, y: 20, z: 30 }; getComponent(vec, x); // OK ``` `const` can be used with arrays too to avoid widening. ```ts const a1 = [1, 2, 3] // Type is number[] const a2 = [1, 2, 3] as const // Type is readonly [1, 2, 3] ``` ??? - TS versucht, eine Balance zwischen Genauigkeit (sprich spezialisierte Typen) und Flexibilität (sprich generalisierte Typen) zu finden. - Mit const ist der Type spezialisierter als mit let. Mit const ist der Fehler von vorhin verschwinden. --- # Undestand type widening and narrowing The opposite of widening is __narrowing__. This is a process by which TypeScript goes from a broad type to a narrower one. There are many ways how you can narrow a type. Just to mention a few: __1__. Using `instanceof` and `typeof` operators. ```ts function contains(text: string, search: string | RegExp) { if (search instanceof RegExp) { return !!search.exec(test); // Type is RegExp } return text.includes(search); // Type is string } ``` __2__. Using built-in functions such as `Array.isArray`. ```ts function contains(text: string, terms: string | string[]) { if (Array.isArray(terms)) { return terms.some(term => text.includes(term)); } return text.includes(terms); // Type is string } ``` ??? narrowing = Verengung von Typen. Das ist ein Prozess, wie aus einem breiten Typ ein enger, spezifischer Typ entsteht. --- # Undestand type widening and narrowing __3__. Using property check. ```ts interface A { a: number } interface B { b: number } function pickAB(ab: A | B) { if ('a' in ab) { // ab is of type A } else { // ab is of type B } // ab is of type A | B } ``` --- # Undestand type widening and narrowing __4__. Using pattern "discriminated union". ```ts interface UploadEvent { type: 'upload'; filename: string; ... } interface DownloadEvent { type: 'download'; filename: string; ... } type AppEvent = UploadEvent | DownloadEvent; function handleEvent(e: AppEvent) { switch (e.type) { case: 'download': // Type of e is DownloadEvent break; case: 'upload': // Type of e is UploadEvent break; } } ``` __5__. Using "user defined type guards". --- # User defined type guards JavaScript doesn't have very rich runtime introspection support built in. When you are using just plain JavaScript Objects, you do not even have access to `instanceof` or `typeof`. For these cases you can create __user defined type guard__ functions. User defined type guard is just a function that returns a type predicate in the form of ```ts someArgumentName is SomeType ``` in place of a return type. For example: ```ts function handle(event: any): event is MouseEvent { // body that returns boolean } ``` If the function returns `true`, TypeScript will narrow the type to `MouseEvent` in any block guarded by a call to the function. In other words, the type will be more specific. `event is MouseEvent` ensures the compiler that the event passed into the type guard is in fact a `MouseEvent`. ??? - User defined type guards können in conditional statements aufgerufen werden. Wäre z.B. die Funktion handle in einem if-statement aufgerufen, würde das event Objekt in dem if-block selbst vom Typ MouseEvent für den TS Compiler. Es findet hier also ein Type narrowing im Code ohne Type Cast (d.h. ein automatischer Type-Cast von any Type auf MouseEvent). - Hier ist wichtig to verstehen, dass der Predicate (Rückgabewert) zur Kompilierzeit evaluiert wird. Funktionskörper wird hingegen zur Laufzeit ausgeführt. Dieses Zusammenspiel ermöglicht es, dass wir ohne Casten auf spezifische Typen programmieren können, ohne dass die Exceptions zur Laufzeit auftreten, wenn der Programmfluss z.B. in einen if-block reinläuft. --- # User defined type guards Let's define two classes `Dog` and `Cat`. ```ts class Pet { } class Dog extends Pet { bark() { console.log('woof'); } } class Cat extends Pet { purr() { console.log('meow'); } } ``` --- # User defined type guards ```ts // user defined type guard function isDog(test: any): test is Dog { return test instanceof Dog; } function example(foo: any) { if (isDog(foo)) { // foo is type as a Dog in this block foo.bark(); } else { // foo is type any in this block console.log("unknown foo"); } } example(new Dog()); example(new Cat()); ``` This prints: ``` woof unknown foo ``` --- # User defined type guards __Keep in mind__ - The function's return type is used at compile time to narrow types. - The function body is used at runtime. Type guard functions can use more complicated logic. E.g. `instanceof` doesn't work with interfaces because they don't exist in runtime => check if an object property exists. ```ts interface Bird { fly(); } interface Fish { swim(); } function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; } ... if (isFish(pet)) { pet.swim(); } else { pet.fly(); } ``` --- # Union types A __union type__ describes a value that can be one of many types. The vertical bar `|` is used as separator for each type the value can have. For instance, ```ts number | string ``` is the type of a value that can be a number or string. For such values, we can only access members that are common to all types in the union. The following code works because the `length` property exists on both strings and arrays: ```ts let value: string | string[] = 'some'; const length = value.length; ``` --- # Union types The next code snippet gives an error because the `model` property does not exist on the `Bike` type: ```ts interface Bike { gears: number; } interface Car { gears: number; model: string; } ``` ```ts let transport: Bike | Car = {gears: 1}; transport.model = "Audi"; // compiler error ``` --- # Intersection types An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need. ```ts type intersection = Person & Serializable & Loggable; ``` `intersection` is a `Person` __and__ `Serializable` __and__ `Loggable`. That means an object of this type will have all members of all three types. Next example: ```ts interface IStudent { id: string; age: number; } interface IWorker { companyId: string; } type A = IStudent & IWorker; // define an intersection type A let x: A = { id: 'ID3241', age: 5, companyId: 'CID5241' }; ``` ??? Probieren, in Playground eine andere Property zu x hinzufügen => Fehler --- # Index types: _keyof_ operator It is worth to mention the __keyof__ operator, also called the __index type query operator__. By using the __keyof__ keyword, we can create a type representing all property keys, which is a union of string literal types. *Definition*: For any type `T`, `keyof T` is the union of known, public property names of `T`. ```ts type UserRole = 'admin' | 'moderator' | 'author'; interface User { id: number; name: string; email: string; role: UserRole; } type UserKeysType = keyof User; // UserKeysType is an union // 'id' | 'name' | 'email' | 'role' ``` --- # Index types: _keyof_ operator Practical example with Generics. ```ts function getProperty
(obj: T, key: K): T[K] { return obj[key]; } ``` `K extends keyof T` means a specific property of `T`. By using this helper we can ensure that the property exists when using it. ```ts const pets = { dog: { name: 'Urmel' }, cat: { name: 'Baghira' } }; getProperty(pets, 'dog'); getProperty(pets, 'cat'); getProperty(pets, 'someoneelse'); // ~~~ Argument of type '"someoneelse"' is not assignable // to parameter of type '"dog" | "cat"' ``` --- # Index types: indexed access operator __Indexed access operator__ allow accessing the type of a property and assigning it to a different type. ```ts interface I { a: string; b: number; } ``` It's possible to get the type of property `a` simply by accessing it. ```ts type PropertyA = I['a']; // PropertyA = string ``` It's also possible to pass multiple properties as a union, which yields a union of the respective property types. ```ts type PropertyTypes = I['a' | 'b']; // PropertyTypes = string | number ``` --- # Index types: indexed access operator The same result can be achieved with `keyof`: ```ts type PropertyTypes = I[keyof I]; // PropertyTypes = string | number ``` Accessing a property that doesn't exist would lead to an error. ```ts type PropertyA = I['nonexistent']; // ~~~ Property 'nonexistent' does not exist on type 'I' ``` ??? Prüfen die Fehlermeldung in Playground --- # Mapped types A common task is to take an existing type and make each of its properties optional or readonly. For example, from ```ts interface Person { name: string; age: number; } ``` we might want ```ts interface PersonPartial { name?: string; age?: number; } interface PersonReadonly { readonly name: string; readonly age: number; } ``` --- # Mapped types TypeScript provides a way to create new types based on old types, called __mapped types__. In a mapped type, the new type transforms each property in the old type in the same way. With mapped types, we can rewrite the two examples above as ```ts type Readonly
= { readonly [P in keyof T]: T[P]; } type Partial
= { [P in keyof T]?: T[P]; } ``` and use them as ```ts type PersonPartial = Partial
; type ReadonlyPerson = Readonly
; ``` --- # Mapped types Let’s take a look at the simplest mapped type and its parts in details: ```ts type Keys = 'option1' | 'option2'; type Flags = { [K in Keys]: boolean }; ``` This is equivalent to ```ts type Flags = { option1: boolean; option2: boolean; } ``` There are three parts: - The type variable `K`, which gets bound to each property in turn. It is achieved by the keyword `in`. - The string literal union `Keys`, which contains the names of properties to iterate over. - The resulting type of the property (here `boolean`). ??? Die Variable K nimmt jeden Wert in Union Keys, d.h. option1, dann option2. Das wird mit dem Schlüsselwort "in" erreicht. Diese Namen bilden die Property-Namen im neuen Typ Flags. Und was sind die Typen von all diesen Properties in Flags? Sie sind alle gleich, und zwar boolean. --- # Mapped types Complex example. Task: wrap every object property in a `Proxy
` class. ```ts type Proxy
= { get(): T; set(value: T): void; } type Proxify
= { [P in keyof T]: Proxy
; } function proxify
(o: T): Proxify
{ // ... wrap proxies ... } let proxyProps = proxify(props); ``` ??? T[P] ist "indexed access operator", den wir bereits kennengelernt haben --- # Conditional types A conditional type selects one of two possible types based on a condition expressed as a type relationship test: ```ts T extends U ? X : Y ``` The type above means when `T` is assignable to `U` the type is `X`, otherwise the type is `Y`. Concrete example: ```ts T extends Function ? string : boolean ``` Usage: ```ts class SomeClass
{ // if the parameter p is a function, the return type is string, // if not it's boolean someFunction
(p: T): T extends Function ? string : boolean; } ``` --- # Conditional types ```ts class AnotherClass
{ // if T is assignable to Foo, the value is number, otherwise string someValue: T extends Foo ? number : string; } ``` Conditional types have a special case, namely if the type parameter to a conditional type is a union. It's called a __distributive conditional type__. In that case, the conditional type is applied separately to each type making up the union. ```ts type T1
= T extends string ? string : boolean; type Union = 'a' | 'b' | true; type T2 = T1
; // T2 = string | boolean ``` What happens here is that `T1` is applied separately to `'a'`, `'b'` and `true` and the results combined back to a union, which yields `string | string | boolean`. The two strings can be combined, so the end result is `string | boolean`. --- # Type inference in conditional types Within the `extends` clause of a conditional type, it is now possible to have __infer__ declarations that introduce a type variable to be inferred. Such inferred type variables may be referenced in the true branch of the conditional type. For example, the following extracts the return type of a function type: ```ts type ReturnType
= T extends (...args: any[]) => infer R ? R : any; ``` Usage: ```ts type T0 = ReturnType<() => string>; // string type T1 = ReturnType<(s: string) => void>; // void ``` Another example: ```ts type First
= T extends [infer U, ...unknown[]] ? U : never; type SomeTupleType = [string, number, boolean]; type FirstElementType = First
; // string ``` --- # Utility types: Omit, Pick, etc. TypeScript provides several utility types to facilitate common type transformations. Available in the standard library __lib.d.ts__. Full documentation:
Utility Types
. We already met `Partial
` and `Readonly
`. Let's meet `Omit` and `Pick`. `Omit
` constructs a type by picking all properties from `T` and then removing `K`. ```ts interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Omit
; const todo: TodoPreview = { title: 'Clean room', completed: false, }; ``` ??? Omit und Pick sind die Utilities, die ich sehr oft nutze. Omit wählt ein Property-Set aus und dann löscht bestimmt Properties aus diesem Set. Das Ergebnis ist ein entschlanktes Property-Set aus der ursprünglich grosseren Propety-Menge. --- # Utility types: Omit, Pick, etc. `Pick
` constructs a type by picking the set of properties `K` from `T`. ```ts interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Pick
; const todo: TodoPreview = { title: 'Clean room', completed: false, }; ``` Another utility types: - __Exclude
__ – Exclude from T those types that are assignable to U. - __Extract
__ – Extract from T those types that are assignable to U. - __NonNullable
__ – Exclude null and undefined from T. - __ReturnType
__ – Obtain the return type of a function type. - __InstanceType
__ – Obtain the instance type of a constructor function type. ??? Pick pickt ein Sub-Set von Properties von einem grosseren Property-Set heraus. So gesehen, ist Pick somit ein Gegenteil von Omit. --- # Decorators __Decorators__ were proposed in ECMAScript 2016 (https://github.com/wycats/javascript-decorators). They are similar to Java annotations - they also add metadata to class declaration, method, property, and the function's parameter, but they are more powerful. They add new behaviors to their targets. With decorators, we can run arbitrary code before, after, or around the target execution, like in aspect-oriented programming, or even replace the target with a new definition. In TypeScript, you can decorate constructors, methods, properties, and parameters. Every decorator begins with the __@__ character followed by the name of the decorator. Let's implement a classic example with a logging functionality. We would like to implement a method decorator `@log`. A method decorator accepts three arguments: - an instance of the class on which the method is defined - a key for the property - and
the property descriptor
If the method decorator returns a value, it will be used as a new property descriptor for this method. ??? Decorators beginnen mit dem Add-Zeichen, das vor dem Namen des Decorators stehen muss. --- # Decorators ```ts const log = (target: Object, key: string | symbol, descriptor: PropertyDescriptor) => { // save a reference to the original method var originalMethod = descriptor.value; // replace the original function descriptor.value = function(...args: any[]) { console.log("Arguments: ", args.join(", ")); const result = originalMethod.apply(target, args); console.log("Result: ", result); return result; } return descriptor; } class Rectangle { @log area(height: number, width: number) { return height * width; } } let rect = new Rectangle(); let area = rect.area(2, 3); ``` ??? Zeigen in TS Playground! Damit der Code mit den Decorators funktioniert, müssen aber zwei spezielle Compiler Optionen enabled werden. Decorators haben immer noch experimentellen Status, obwohl sie intensiv in Angular und den anderen Web-Frameworks verwendet werden. Das überrascht ein wenig, ist aber so. --- # Decorators Decorators can be composed and customized with parameters too. You can write the following, for instance: ```ts class Rectangle { @log("debug") @persist("localStorage") area(height: number, width: number) { return height * width; } } ``` Angular offers different types of decorators that are used for dependency injection or adding metadata information at compilation time: - Class decorators such as `@NgModule`, `@Component`, and `@Injectable` - Property decorators such as `@Input` and `@Output` - Method decorators such as `@HostListener` - Parameter decorators such as `@Inject` --- # Decorators TypeScript compiler is able to emit some design-time type metadata for decorators. To access this information, we have to install a Polyfill called __reflect-metadata__: ``` npm install reflect-metadata --save ``` As long as the reflect-metadata library has been imported, additional design-time type information will be exposed at runtime. We can now access, for example, the type of the property (`key`) on the `target` object as follows: ```ts let typeOfKey = Reflect.getMetadata("design:type", target, key); ``` ❗ Refer to the official TypeScript documentation to learn more about decorators and reflect metadata API (https://www.typescriptlang.org/docs/handbook/decorators.html) ❗ Decorators are enabled by setting the compiler options `emitDecoratorMetadata` and `experimentalDecorators` to `true`. ??? Polyfill wird benötigt, weil Decorators, wie ich bereits gesagt habe, ein experimentales Feature ist, welches z.Z. von keinem Browser nativ unterstützt wird. --- # Mixins - composing partial behaviors __Mixin__ is the process of combining multiple classes to a single target class. It is intended to overcome the limitations of single inheritance model of JavaScript. In TypeScript, we can't inherit or extend from more than one class with "extends" but mixins helps us to get around that. Mixins create partial classes which we can combine to form a single class that contains all the methods and properties from the partial classes. From a mathematical point of view, one can say that the classic, single super-class inheritance creates a tree. And mixin pattern creates a directed acyclic graph. |
|
| |:---:|:---:| ??? - JavaScript unterstützt von der Sprache her, wie viele anderen Programmiersprachen auch, keine Mehrfachvererbung. Mit dem Schlüsselwort "extends" kann man nur noch von einer Klasse die Methoden und die Properties erben. TS ist ein typisiertes Superset von JS und hat diese Einschränkung auch. Mixin ist ein Prozess, welcher diese Einschränkung umgeht, und eine Mehrfachvererbung in TypeScript ermöglicht. - Mixins ist also die Lösung, um neue Klassen aus mehreren anderen zu erstellen. Das ist eine Art "Composition", die uns erlaubt, einzelne Klassen mit Behaviors wiederzuverwenden, indem man sie beliebig kombiniert. Die kombinierte Klasse enthält alle Methoden und Properties der einzelnen Klassen. --- # Mixins - composing partial behaviors Let's create a `Timestamped` mixin that tracks the creation date of an object in a `timestamp` property: ```ts // The type Constructor
is an alias for the construct signature // that describes a type which can construct objects // of the generic type T and whose constructor function accepts // an arbitrary number of parameters of any type. type Constructor
= new (...args: any[]) => T; function Timestamped
(Base: TBase) { return class extends Base { timestamp = Date.now(); }; } ``` ??? Wie man sieht, ein Mixin ist technisch eine Function, die eine anonyme Klasse zurückgibt. Hier ist das die function Timestamped. Die zurückgegebene Klasse erbt von der Klasse, die als Parameter an die Mixin function übergeben wird. Hier wird Base vom Typ TBase übergeben. "TBase extends Constructor" heisst, dass Base nichts anders ist, als eine beliebige Klasse, weil jede Klasse einen Constuctor hat. --- # Mixins - composing partial behaviors Let's create a `User` class ```ts class User { name: string; constructor(name: string) { this.name = name; } } ``` and a new class by mixing `Timestamped` into `User`. ```ts const TimestampedUser = Timestamped(User); // Instantiate the new `TimestampedUser` class const user = new TimestampedUser("John Doe"); // We can now access properties from both the `User` class // and our `Timestamped` mixin in a type-safe manner console.log(user.name); console.log(user.timestamp); ``` ??? TS versteht Mixins Syntax und bietet hier Autocompletion. Prüfen in TS Playground. --- # Mixins - composing partial behaviors Let's define another mixin with a constructor and use it. ```ts function Tagged
(Base: TBase) { return class extends Base { tag: string | null; constructor(...args: any[]) { super(...args); this.tag = null; } }; } ``` ```ts // Create a new class by mixing `Tagged` into `User` const TaggedUser = Tagged(User); // Instantiate the new `TaggedUser` class const user = new TaggedUser("John Doe"); user.name = "Jane Doe"; user.tag = "janedoe"; ``` --- # Mixins - composing partial behaviors Mixin can have methods too because it returns a regular ES2015 class. E.g. ```ts function Activatable
(Base: TBase) { return class extends Base { isActivated = false; activate() { this.isActivated = true; } deactivate() { this.isActivated = false; } }; } ``` Let's compose all the mixins! ```ts const SpecialUser = Activatable(Tagged(Timestamped(User))); const user = new SpecialUser("John Doe"); ``` --- # Type definition files JavaScript programs written in native JavaScript don't have any type information. If you add a JavaScript library such as jQuery or Lodash to your TypeScript-based application and try to use it, the TypeScript compiler cannot find any type information and warn you with compilation errors. Compile-time safety, type checking, and context-aware code completion get lost. That is where __type definition files__ come into play. Type definition files provide type information for JavaScript code that is not statically typed. Type definition files ends with `.d.ts` and only contain definitions which are not emitted by TypeScript. The __declare__ keyword is used to add types to JavaScript code that exists somewhere. Let's take an example. TypeScript is shipped with the `lib.d.ts` library describing ECMAScript API. This type definition file is used automatically by the TypeScript compiler. The following declaration is defined in this file without implementation details: ```ts declare function parseInt(s: string, radix?: number): number; ``` Now, when you use the parseInt function in your code, the TypeScript compiler ensures that your code uses the correct types and IDEs show context-sensitive hints when you're writing code. ??? Probieren in TS Playground ohne und mit declare: parseSomething(123) und dann parseSomething("Hello"); ```ts // declare function parseSomething(s: string, radix?: number): number parseSomething(123); ``` --- # Type definition files Type definition files can be installed as dependencies under the `node_modules/@types` directory by typing the following command: ``` npm install @types/
--save-dev ``` A concrete example for jQuery library is: ``` npm install @types/jquery --save-dev ``` ❗ In Angular, all type definition files are bundled with Angular npm packages and located under node_modules/@angular. There is no need to install such files separately like we did for jQuery. TypeScript finds them automatically. ❗ If you have to support old browsers, you have to set the compile target ES5. Assume you want to use some ES6 (ECMAScript 2015) features by adding Polyfills. In this case, you must tell the compiler that it should look for extended definitions in the `lib.es6.d.ts` or `lib.es2015.d.ts` file. ``` "lib": ["es2015", "dom"] // settings in tsconfig.json ``` --- # Latest TypeScript features: optional chaining. Optional chaining operator __?.__ is a new JavaScript operator, also available in TypeScript. Optional chaining lets us write code where TypeScript can immediately stop running some expressions if we run into a `null` or `undefined`. Let's write: ```ts let x = foo?.bar.baz(); ``` This means: - when `foo` is defined, `foo.bar.baz()` will be computed - but when `foo` is `null` or `undefined`, just return `undefined` This is the same as ```ts let x = (foo === null || foo === undefined) ? undefined : foo.bar.baz(); ``` ??? - Neue JavaScript Sprachkonstrukte bringen signifikante Verbesserungen für Programmierer. Sie sind logischerweise auch in den letzten TS Versionen verfügbar, weil TS ein Superset von JS ist. Wird der JS Code für ältere Browser generiert wird, kann dieses Feature polyfilled werden. - Prüfen in TS Playground. Das Feature ist mit ES2020 verfügbar! - Der Optionale-Verkettungs-Operator (Optional Chaining) ?. ermöglicht es, einen Wert aus einer Eigenschaft tief innerhalb einer Verkettung von Objekt-Eigenschaften auszulesen, ohne dabei explizit überprüfen zu müssen, ob jede Referenz in der Kette valide ist. Operator funktioniert ähnlich wie der Verkettungs Operator ., außer dass er keinen Fehler bei einem null oder undefined Wert auswirft, sondern stattdessen den Ausdruck beendet und undefined zurückgibt. Wird er mit Funktionsaufrufen verwendet, wirft der Aufruf undefined zurück, wenn die Funktion nicht existiert. --- # Latest TypeScript features: optional chaining. Note that if `bar` is `null` or `undefined`, the code will still hit an error accessing `baz`. Let's check `bar` too. ```ts foo?.bar?.baz(); ``` This is the same as ```ts if (foo && foo.bar) { foo.bar.baz(); } ``` It works with functions too. ```ts let result = someClass.someMethod?.(); ``` `result` is undefined if the method `someMethod` doesn't exist. ??? - Man sieht, wir sparen einiges an Schreibarbeit und der Code wirkt kompakter. - Verwendet man optionale Verkettung mit Funktionsaufrufen, wird der Ausdruck automatisch undefined zurückwerfen, sollte die Funktion nicht existieren. --- # Latest TypeScript features: optional chaining. ```ts function doSomething(onContent, onError) { try { // ... do something with the data } catch (err) { onError?.(err.message); // no exception if onError is undefined // equivalent to // if (onError) { // onError(err.message); // } } } ``` You can also use the optional chaining operator when accessing properties with an expression using the bracket notation of the property accessor. Array item access with optional chaining works as well. ```ts let nestedProp = obj?.['prop' + 'Name']; let arrayItem = arr?.[42]; ``` --- # Latest TypeScript features: nullish coalescing. The nullish coalescing operator __??__ is another upcoming ECMAScript feature. It is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand. ```ts let x = foo ?? bar(); ``` This is a new way to say that the value `foo` will be used when it's "present"; but when it's null or undefined, calculate `bar()` in its place. The above code is equivalent to ```ts let x = (foo !== null && foo !== undefined) ? foo : bar(); ``` --- # Latest TypeScript features: nullish coalescing. ```ts const nullValue = null; const emptyText = ""; // falsy const someNumber = 42; const valA = nullValue ?? "default for A"; const valB = emptyText ?? "default for B"; const valC = someNumber ?? 0; console.log(valA); // "default for A" console.log(valB); // "" (as empty string is not null or undefined) console.log(valC); // 42 ``` __Q:__ Why do we need nullish coalescing? __A:__ When using short-circuiting operator `&&` and `||`, we need to handle all `falsy` values such as
`0`
,
`NaN`
,
`""`
. ??? NaN entsteht z.B. durch das Dividiren durch 0. --- # Latest TypeScript features: nullish coalescing. The `??` operator can replace uses of `||` when trying to use a default value. The following code snippet tries to fetch the volume that was last saved in `localStorage`; however, it has a bug because it uses `||`. ```ts function initializeAudio() { let volume = localStorage.volume || 0.5 // ... } ``` When `localStorage.volume` is set to `0`, the page will set the volume to `0.5` which is unintended. `??` avoids some unintended behavior from `0`, `NaN` and `""` being treated as `falsy` values. --- # Latest TypeScript features: support for ECMAScript private fields. TypeScript 3.8 brings support for ECMAScript's private fields. TypeScript's `private` modifier only works at compile time! Private fields with the `private` modifier can be accessed in the generated JavaScript at runtime. ECMAScript's private fields bring the truly support for privacy. Such fields can't be accessed or detected outside of the containing class. Private fields have a few _rules_: - Private fields start with a __#__ character. - Every private field name is uniquely scoped to its containing class. - TypeScript accessibility modifiers like `public` or `private` can't be used on private fields. Browser support, see https://caniuse.com/#search=private%20fields --- # Latest TypeScript features: support for ECMAScript private fields. Example: ```ts class Person { #name: string constructor(name: string) { this.#name = name; } greet() { console.log(`Hello, my name is ${this.#name}!`); } } let jeremy = new Person("Jeremy Bearimy"); jeremy.#name // ~~~~~ // Property '#name' is not accessible outside class 'Person' // because it has a private identifier. ``` --- # Latest TypeScript features: support for ECMAScript private fields. Another benefit of private fields is that uniqueness. For example, regular property declarations are prone to being overwritten in subclasses. ```ts class C { foo = 10; cHelper() { return this.foo; } } class D extends C { foo = 20; dHelper() { return this.foo; } } let instance = new D(); console.log(instance.cHelper()); // prints '20' console.log(instance.dHelper()); // prints '20' ``` --- # Latest TypeScript features: support for ECMAScript private fields. With private fields, you'll never have to worry about this, since each field name is unique to the containing class. ```ts class C { #foo = 10; cHelper() { return this.#foo; } } class D extends C { #foo = 20; dHelper() { return this.#foo; } } let instance = new D(); console.log(instance.cHelper()); // prints '10' console.log(instance.dHelper()); // prints '20' ``` --- # Latest TypeScript features: static class blocks. Since TypeScript 4.4 we can use _class static blocks_. This is also an ECMAScript feature since ES2022. It's used to write any initialization code for the class (static members). ```ts class Foo { static #count = 0; static { if (someCondition()) { Foo.#count++; } console.log(Foo.#count); } #someCondition() {...} } ``` - In class static blocks, you can access all private internals. - You can also create multiple static blocks in a class, which will be executed in sequential order. --- # Latest TypeScript features: _satisfies_ operator. The _satisfies_ operator exists since TypeScript 4.9. The purpose of this operator is to enforce a constraint on a variable, without changing its type. For instance, we want to say that a color is either a string, or a RGB tuple. ```ts type RGB = readonly [red: number, green: number, blue: number]; type Color = { value: RGB | string }; const aColor: Color = { value: 'red' }; ``` But now, TypeScript doesn't know whether `aColor.value` is a string or a tuple. If we try to call `aColor.value.toUpperCase()`, there is an error at compile time (even if the value is a string): __Property 'toUpperCase' does not exist on type 'string | RGB'__. It works with `satisfies` ```ts const aColor = { value: 'red' } satisfies Color; aColor.value.toUpperCase(); // works ``` --- # Latest TypeScript features: _satisfies_ operator. Another example from Angular. ```ts type Route = { path: string; children?: Routes } type Routes = Record
const routes: Routes = { HOME: { path: "/home", }, } ``` When we go to use the routes object, the compiler has no idea what the actual configured routes are. This compiles just fine, but would throw errors at runtime: ```ts routes.SOME.path ``` --- # Latest TypeScript features: _satisfies_ operator. The solution: combining `satisfies` with `as const`. ```ts const routes = { HOME: { path: '/' } } as const satisfies Routes navigate(routes.HOME.path) ``` Type of ```ts routes.HOME.path ``` is now `'/'`. Without `as const` the type would be `string`. --- # Best practice tips. Prefer unions of interfaces to interfaces of unions. If you create an interface whose properties are union types, you should ask whether the type would make more sence as union of more precise interfaces Suppose you're building a drawing program and want to define an interface for layers with specific geometry types. ```ts interface Layer { geometry: PointGeometry | LineGeometry | TriangleGeometry style: PointStyle | LineStyle | TriangleStyle } ``` __Question:__ What could be a problem here? -- __The problem:__ Would it make sence to have a layer whose `geometry` is `LineGeometry` but whose `style` is `TriangleStyle`? Obvious not. Allowing this possibility makes using the library more error-prone and makes this interface difficult to work with. --- # Best practice tips. Prefer unions of interfaces to interfaces of unions. A better way to model this is with separate interfaces for each type of layer: ```ts interface PointLayer { geometry: PointGeometry; style: PointStyle; } interface LineLayer { geometry: LineGeometry; style: LineStyle; } interface TriangleLayer { geometry: TriangleGeometry; style: TriangleStyle; } type Layer = PointLayer | LineLayer | TriangleLayer; ``` __Advantage:__ By defining `Layer` in this way, you've excluded the possibility of mixed `geometry` and `style` properties. --- # Best practice tips. Prefer unions of interfaces to interfaces of unions. Even better with a `type` property which can be used to determine which type of `Layer` you're working with at runtime. ```ts interface PointLayer { type: 'point'; geometry: PointGeometry; style: PointStyle; } interface LineLayer { type: 'line'; geometry: LineGeometry; style: LineStyle; } interface TriangleLayer { type: 'triangle'; geometry: TriangleGeometry; style: TriangleStyle; } type Layer = PointLayer | LineLayer | TriangleLayer; ``` --- # Best practice tips. Prefer unions of interfaces to interfaces of unions. TypeScript is also able to narrow the type of `Layer` based on the "tag" property `type`. ```ts function drawLayer(layer: Layer) { if (layer.type === 'point') { const {geometry} = layer; // Type is PointGeometry const {style} = layer; // Type is PointStyle } else if (layer.type === 'line') { const {geometry} = layer; // Type is LineGeometry const {style} = layer; // Type is LineStyle } else { const {geometry} = layer; // Type is TriangleGeometry const {style} = layer; // Type is TriangleStyle } } ``` ??? Habe gar nicht gefragt, kennt ihr das Gleicheitszeichen mit drei Zeichen? Mit dieser Schreibweise werden auch die Typen verglichen (zu empfehlen). Bsp: 0 == false gibt true, aber 0 === false gibt false. --- # Best practice tips. Use async functions instead of callbacks. Classic JavaScript modeled asynchronous behavior using callbacks. ```ts function fetchURL(url: string, cb: (response: string) => void) { cb(url); } // "pyramid of doom" fetchURL(url1, function(response1) { fetchURL(url2, function(response2) { fetchURL(url3, function(response3) { console.log(1); }); console.log(2); }); console.log(3); }); // 3 // 2 // 1 ``` --- # Best practice tips. Use async functions instead of callbacks. __Problems with callbacks__: - Execution order is the opposite of the code order. - The code is hard to read. - Difficult to run requests in parallel. ES2015 introduced the concept of a __Promise__ to break the pyramid of doom. A Promise represent something that will be available in the future. Example of a Promise that will be resolved in 2000 ms: ```ts let timeoutPromise = new Promise(function(resolve, reject) { setTimeout(resolve, 2000); }); ``` Example of `Promise.all` to execute multiple tasks in parallel: ```ts Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); }); ``` ??? - Promise ist, auf Deutsch gesagt, ein Versprechens-Objekt, das später eingelöst wird. Promises werden für asynchrone Berechnungen verwendet. - Mit Promise können asynchrone Methoden in gleicher Weise Werte zurück geben, wie synchrone Methoden: Und zwar, anstelle des endgültigen Wertes wird ein Promise zurückgegeben, zu welchem zu einem Zeitpunkt in der Zukunft einen Wert geben wird. - Promise.all wird typischerweise für mehrere asynchrone Aufgaben (Tasks), die parallel laufen, verwendet. Alle Tasks liefern Promises als Ergebnisse zurück. Mit Promise.all wird gewartet, bis alle anderen Tasks abgeschlossen sind. - "values" ist ein Array von Rückgabewerten, die einzelne Promises bereitstellen, wenn sie resolved sind. --- # Best practice tips. Use async functions instead of callbacks. The original code with Promise instead of callbacks: ```ts fetch(url1).then(response1 => { return fetch(url2); }).then(response2 => { return fetch(url3); }).then(response3 => { // ... }).catch(error => { // ... }); ``` Can we write this better? Yes. ES2017 introduced the `async` and `await` keywords. --- # Best practice tips. Use async functions instead of callbacks. ```ts async function fetchPages() { try { const response1 = await fetch(url1); const response2 = await fetch(url2); const response3 = await fetch(url3); // ... } catch (e) { // ... } } ``` The `await` keyword pauses execution of the `fetchPages` function until each Promise resolves. `async` / `await` allow to write the code in a synchronous manner. Until ES2022 we could only use the `await` operator in an `async` function. Since ES2022 we can use this operator in the global scope (at the top level) as well. Browser support, see https://caniuse.com/#search=async ??? - async / await erlauben uns, den Code in einer Weise zu schreiben, wie man diese von einem gewöhnlichen synchronen Programmablauf kennt. - await wartet bis der Promise aufgelöst (also resolved) ist. Das Ergebnis kann einer Variable zugewiesen werden, wie beispielsweise hier der Variable "response1". - Hat eine Methode ein await, muss sie zwingend mit async annotiert werden. Der Schlüsselwort async steht direkt vor dem Schlüsselwort "function". - Dieser Ansatz mit async / await beseitigt auch alle Mankos, die ich bei Callbacks erwähnt habe. Der Code ist viel besser lesbar. --- class: center, middle, inverse # That's all folks, thanks for your attention! .back[[Back to the homepage](https://ova2.github.io/typescript-rowing-session/)] Slideshow created with [remark](https://github.com/gnab/remark)