---
theme: gaia
_class: lead
paginate: true
backgroundColor: #00437a
color: #fff
---

# **Typescript**

![bg left:40% 80% drop-shadow:0,0,0,#fff](img/IServ_Logo_White.png)

Endlich ruhig schlafen bei der Frontend-Entwicklung. 😴

---

# Was ist Typescript?

Zusatzinformationen über Typen für Javascript.

---

# Fehlt es Javascript an Types?

```js
var data; // Was darf hier rein?
```

---

# Fehlt es Javascript an Types?
```js
> var data = "some string";
> console.log(data);
"some string"
> data = 15; // Why not both?
> console.log(data);
15

```
---

# Jetzt mit Types!

---

# Jetzt mit Types!

```ts
var data: string = "some string";
console.log(data);
data = 15; // ❌ Error
console.log(data);
```

---

## Transparenz von Schnittstellen

Was tut diese Library?
```js
// @iserv/graph.js
export function drawGraph(data, options = {}) {
    [... Implementierungsdetails]
}
```

---

## Transparenz von Schnittstellen

Was tut diese Library?
```js
// @iserv/graph.js
export function drawGraph(data, options = {}) {
    [... Implementierungsdetails]
}
```

Besser: Wie benutze ich diese Library?

---

## Transparenz von Schnittstellen

```js
// @iserv/graph.js
export function drawGraph(data, options = {}) {
    [... Implementierungsdetails]
}

// your-module.js
drawGraph([{
    x: 2,
    y: 5,
}, {
    x: 3,
    y: 7,
}]);
```

---

## Transparenz von Schnittstellen

```js
// @iserv/graph.js
export function drawGraph(data, options = {}) {
    [... Implementierungsdetails]
}

// your-module.js
drawGraph([
    5,
    7
]);
```

---

## Transparenz von Schnittstellen

```js
// @iserv/graph.js
export function drawGraph(data, options = {}) {
    [... Implementierungsdetails]
}

// your-module.js
drawGraph([
    5,
    7
], {
    start: 0,
    stepSize: 1,
});
```

---

## Dafür hat man doch JSDoc!

```js
// @iserv/graph.js
/**
 * @typedef {Object} GraphOptions
 * @property {HTMLCanvasElement} mount
 * @property {number} start
 * @property {number} stepSize
 *
 * @param {GraphOptions} options
 */
export function drawGraph(data, options = {}) {
    [...Implementierungsdetail]
}
// your-module.js
drawGraph(
    data,
    {
        mount: document.getElementById('my-canvas'),
        start: 2,
        stepSize: 1,
    },
)
```

---

## Warum nicht JSDoc?

![bg left:50% 100%](img/self-documenting.png)
* JSDoc kann lügen
* Wird nicht automatisch überprüft

---

## Typescript aufsetzen

![bg left:50% 210%](img/busy-doggo.png)

* In neuem Projekt
* Integration mit Webpack

---

## Typescript in einem neuen Projekt

Installieren

```
npm install -g typescript         # global
npm install --save-dev typescript # lokal
```

---

## Typescript in einem neuen Projekt

Neues Projekt

```
tsc --init
```

* (Lokal ist der Befehl dann `npx tsc [...]`)

---

## Konfiguration

tsconfig.json
```json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}
```

---

## Kompilieren

```
tsc [<file>]
```

---

## Kompilieren
```ts
// index.ts
let variable: number = 5;

variable = 10;
```
<one `tsc` later>
```js
// index.js
"use strict";
let variable = 5;
variable = 10;
```
---
## Integration in Webpack-Projekten


---
## Integration in Webpack-Projekten

![bg 90%](img/webpack-typescript-bundling.png)

---

## Integration in Webpack-Projekten
```
npm install --save-dev typescript ts-loader
                                  ^^^^^^^^^
```

---
## Integration in Webpack-Projekten
```js
// webpack.config.js
module.exports = {
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
        ],
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    // [...]
};
```
---

## Integration in bestehende Projekte

![bg left:50% 100%](img/typescript-integration.png)

---

## Integration in bestehende Projekte

![bg left:50% 100%](img/typescript-integration.png)

![w:150](img/squarehole.png)

---

## Typescript in Javascript?

* TS in JS integrieren geht ohne Probleme
* ```ts
  // extension.ts
  export function getFileExtension(path: string): string {
      return path.split(".").slice(-1);
  }
  // path.js
  import {getFileExtension} from './extension.ts';
  console.log(getFileExtension("some/image.png"));
  // > "png"
  ```
---

## Javascript in Typescript?
* JS in TS integrieren ist schwierig
* ```ts
  // extension.js
  export function getFileExtension(path) {
      return path.split(".").slice(-1);
  }
  // path.ts
  import {getFileExtension} from './extension.ts';
  console.log(getFileExtension("some/image.png"));
  ```

---
## Javascript in Typescript
```
path.ts:1:32 - error TS7016: Could not find a declaration file
 for module './extension.js'.
'./extension.js' implicitly has an 'any' type.

1 import {getFileExtension} from './extension.js';
                                 ~~~~~~~~~~~~~~~~
Found 1 error in path.ts:1
```

---

![bg 60%](img/typescript-migration-1.png)

---

![bg 60%](img/typescript-migration-2.png)

---

![bg 60%](img/typescript-migration-3.png)

---

## Javascript in Typescript

Wie damit umgehen?
* Typescript auf weniger strikt stellen
    * => Nutzung von Typescript verliert an Wert
* Alle Abhängigkeiten direkt nach Typescript konvertieren
    * Nicht immer machbar
* Schnittstellen mit Typen versehen

---

![bg 60%](img/typescript-migration-4.png)

---

## Schnittstellen typisieren

```ts
// extension.d.ts
export function getFileExtension(path: string): string;
```

* Keine Implementierung
* Kein Type-Checking der Implementierung
* Schnittstellen werden wenigstens typisiert `¯\_(ツ)_/¯`
* Das geht auch mit JS-Libraries

---

## Wie beschreibe ich nun meine Daten?

```js
const workerBee = {
    role: 'worker',
    name: 'Larry',
    age: 15,
};
```

---

## Objekte

```ts
const workerBee: Bee = {
    role: 'worker',
    name: 'Larry',
    age: 15,
};
```

```ts
type Bee = {
    role: string,
    name: string,
    age: number,
};
```

---

## Semantische Typen

```ts
const workerBee: Bee = {
    role: 'worker',
    name: 'Larry',
    age: 15,
};
```

```ts
type Bee = {
    role: BeeRole,
    name: string,
    age: Months,
};
type BeeRole = 'worker'|'queen'|'guard';
type Months = number;
```

---

![bg 60%](img/bee-type.png)

---

## Dictionaries

```js
const userNames = {
    '652a4707-3043-4ca9-b417-24feac0f5953': 'Bernhard',
    '017daef7-e1f1-4160-81ff-e5b17e86fc6b': 'Frieda',
};
```

---

## Dictionaries

```ts
const userNames: UserNameDictionary = {
    '652a4707-3043-4ca9-b417-24feac0f5953': 'Bernhard',
    '017daef7-e1f1-4160-81ff-e5b17e86fc6b': 'Frieda',
};
type UserId = string;
type UserNameDictionary = Record<UserId, string>;
```
---

## Dictionaries (alternative)

```ts
const userNames: UserNameDictionary = {
    '652a4707-3043-4ca9-b417-24feac0f5953': 'Bernhard',
    '017daef7-e1f1-4160-81ff-e5b17e86fc6b': 'Frieda',
};
type UserId = string;
type UserNameDictionary = {
    [UserId]: string;
};
```

---

## Optionale Properties

```js
const lkw = {
    anzahlRaeder: 18,
    hubraum: 25,
};
const bobbycar = {
    anzahlRaeder: 4,
};
```

---

## Optionale Properties

```ts
const lkw: Car = {
    anzahlRaeder: 18,
    hubraum: 25,
};
const bobbycar: Car = {
    anzahlRaeder: 4,
};
type Kubikmeter = number;
type Car = {
    anzahlRaeder: number,
    hubraum?: Kubikmeter,
};
```

---

## Nur optionale Properties

```ts
type Vehicle = Partial<{
    anzahlRaeder: number,
    hubraum: Kubikmeter,
    anzahlKufen: number,
}>;
```
![bg right:60% 100%](img/bobbycar.png)
![bg right:60% 130%](img/hovercraft.png)
![bg right:60% 170%](img/hubschraubär.png)

---

## Nur optionale Properties

```ts
type Vehicle = Partial<{
    anzahlRaeder: number,
    hubraum: Kubikmeter,
    anzahlKufen: number,
}>;
const helikopter: Vehicle = {
    anzahlKufen: 2,
};
```

---

## Nur optionale Properties

```ts
type Vehicle = Partial<{
    anzahlRaeder: number,
    hubraum: Kubikmeter,
    anzahlKufen: number,
}>;
const helikopter: Vehicle = {
    anzahlKufen: 2,
};
const bobbyCar: Vehicle = {
    anzahlRaeder: 4,
};
```

---

## Nur optionale Properties

```ts
type Vehicle = Partial<{
    anzahlRaeder: number,
    hubraum: Kubikmeter,
    anzahlKufen: number,
}>;
const helikopter: Vehicle = {
    anzahlKufen: 2,
};
const bobbyCar: Vehicle = {
    anzahlRaeder: 4,
};
const luftKissenFahrzeug: Vehicle = {
    hubraum: 10,
};
```

---

## Readonly

```ts
// @iserv/colors.ts
export type Color = string;
export type Colors = Record<'RED'|'GREEN'|'BLUE', Color>
export const colors: Colors = {
    RED: '#ff0000',
    GREEN: '#00ff00',
    BLUE: '#0000ff',
};
```

---

## Readonly

```ts
// @iserv/colors.ts
export type Color = string;
export type Colors = Record<'RED'|'GREEN'|'BLUE', Color>
export const colors: Colors = {
    RED: '#ff0000',
    GREEN: '#00ff00',
    BLUE: '#0000ff',
};
```
```ts
// consumer.ts
import {colors} from '@iserv/colors.ts';
colors = {}; // verboten wegen const
```

---

## Readonly

```ts
// @iserv/colors.ts
export type Color = string;
export type Colors = Record<'RED'|'GREEN'|'BLUE', Color>
export const colors: Colors = {
    RED: '#ff0000',
    GREEN: '#00ff00',
    BLUE: '#0000ff',
};
```
```ts
// consumer.ts
import {colors} from '@iserv/colors.ts';
colors = {}; // verboten wegen const
colors.BLUE = colors.RED; // erlaubt 😢
```

---

## Readonly

```ts
// @iserv/colors.ts
export type Color = string;
export type Colors = Record<'RED'|'GREEN'|'BLUE', Color>
export const colors: Readonly<Colors> = {
    RED: '#ff0000',  ^^^^^^^^
    GREEN: '#00ff00',
    BLUE: '#0000ff',
};
```
```ts
// consumer.ts
import {colors} from '@iserv/colors.ts';
colors = {}; // verboten wegen const
colors.BLUE = colors.RED; // verbiet 😎
```

---

## Typen kombinieren

```ts
type Renderable = {
    render: () => void,
}
type Updateable = {
    update: () => void,
}

const gameObject = {
    render: () => {},
    update: () => {},
};
```

---

## Typen kombinieren - Union

```ts
type Renderable = {
    render: () => void,
}
type Updateable = {
    update: () => void,
}
type GameObject = Renderable & Updateable;
const gameObject: GameObject = {
    render: () => {},
    update: () => {},
};
```

---

## Typen kombinieren

```ts
type NetworkPacket = {
    data: Record<string, string>,
    msg: string,
};
type Exception = {
    trace: string[],
    msg: string,
}
```


---

## Typen kombinieren - Intersection

```ts
type NetworkPacket = {
    data: Record<string, string>,
    msg: string,
};
type Exception = {
    trace: string[],
    msg: string,
}
type FetchResult = NetworkPacket | Exception;
let fetch: () => FetchResult;
const fetchResult: FetchResult = fetch();
```

---

## Typen kombinieren - Intersection

```ts
type NetworkPacket = {
    data: Record<string, string>,
    msg: string,
};
type Exception = {
    trace: string[],
    msg: string,
}
type FetchResult = NetworkPacket | Exception;
let fetch: () => FetchResult;
const fetchResult: FetchResult = fetch();
console.log(fetchResult.msg);
```

---

## Typen kombinieren - Intersection

```ts
type NetworkPacket = {
    data: Record<string, string>,
    msg: string,
};
type Exception = {
    trace: string[],
    msg: string,
}
type FetchResult = NetworkPacket | Exception;
let fetch: () => FetchResult;
const fetchResult: FetchResult = fetch();
if ('trace' in fetchResult) {
    // fetchResult is Exception
    console.log(fetchResult.trace);
}
```
---

## Typen kombinieren - Intersection

```ts
type NetworkPacket = {
    data: Record<string, string>,
    msg: string,
};
type Exception = {
    trace: string[],
    msg: string,
}
type FetchResult = NetworkPacket | Exception;
let fetch: () => FetchResult;
const fetchResult: FetchResult = fetch();
if ('data' in fetchResult) {
    // fetchResult is NetworkPacket
    console.log(fetchResult.data);
}
```

---

## Duck Typing

![bg right:60%](img/duck.png)

* > *If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.*

---

## Type narrowing

```ts
const ducks: unknown = await fetch('https://api.ducks.com/sorts.json');
```

---

## Type narrowing

```ts
const ducks: unknown = await fetch('https://api.ducks.com/sorts.json');
type DuckSort = {
    name: string,
    id: number,
    species: string,
    habitat: string[],
}
```

---

## Type narrowing via Type-Guards

```ts
const ducks: unknown = await fetch('https://api.ducks.com/sorts.json');
type DuckSort = {
    name: string,
    id: number,
    species: string,
    habitat: string[],
}
function isDuckSort(duckSort: unknown): duckSort is DuckSort {
    if (!('name' in duckSort) || typeof duckSort['name'] !== 'string') {
        return false;
    }
    // [...]
}
if (isDuckSort(ducks)) {
    // 'ducks' can be used now
}
```

---

![bg](img/manually.png)

---

## Automatische Type-Guard Generierung 

* `github.com/rhys-vdw/ts-auto-guard`
* Generiert Type-Guards automatisch
* ```ts
  /** @see {isDuckSort} ts-auto-guard:type-guard */
  export type DuckSort = {/** ... */};
  ```
* `npx ts-auto-guard`

---

## ts-auto-guard Beispiel

```ts
export function isDuckSort(obj: unknown): obj is DuckSort {
    const typedObj = obj as DuckSort
    return (
        (typedObj !== null &&
            typeof typedObj === "object" ||
            typeof typedObj === "function") &&
        typeof typedObj["name"] === "string" &&
        typeof typedObj["id"] === "number" &&
        typeof typedObj["species"] === "string" &&
        Array.isArray(typedObj["habitat"]) &&
        typedObj["habitat"].every((e: any) =>
            typeof e === "string"
        )
    )
}
```

---

## Bad Practices

```ts
const config = readFromFile('config.yml') as Config;
```

* `as`-Operator ist der Holzhammer
* Typescript glaubt dir alles
* Führt potenzielle Bugs ein

---

## `as`-Operator

```ts
const number = {name: 'blarg'} as number;
```
* ![trustmebro.png](img%2Ftrustmebro.png)
* Fauler Ersatz für Type-Guarding
* Panzerband für Typescripts Deduktions-Schwächen

---

## Deduktionsschwächen

```ts
function inferBroken(str: string) {
    if (['Bernd', 'Margeret'].includes(str)) {
        const narrowed: 'Bernd'|'Margeret' = str;
    }
}
```
```
error TS2322: Type 'string' is not assignable to type '"Bernd" | "Margeret"'.
```

---

## Deduktionsschwächen - Do NOT
```ts
function inferBroken(str: string) {
    if (['Bernd', 'Margeret'].includes(str)) {
        const narrowed = str as 'Bernd'|'Margeret';
    }
}
```


---

## Deduktionsschwächen - Do NOT
```ts
function inferBroken(str: string) {
    if (['Jürgen', 'Marilla'].includes(str)) {
        const narrowed = str as 'Bernd'|'Margeret'; // Wirft keine Fehler 😢
    }
}
```

* Mit guard-clauses wäre das nicht passiert

---

## Reicht jetzt auch

![bg right:60%](img/call-it-a-draw)