Files

961 lines
15 KiB
Markdown
Raw Permalink Normal View History

2023-06-06 20:13:11 +02:00
---
theme: gaia
_class: lead
paginate: true
2023-06-06 23:55:46 +02:00
backgroundColor: #00437a
color: #fff
2023-06-06 20:13:11 +02:00
---
2023-06-30 18:54:33 +02:00
# **Typescript**
2023-06-06 20:13:11 +02:00
2023-06-30 18:54:33 +02:00
![bg left:40% 80% drop-shadow:0,0,0,#fff](img/IServ_Logo_White.png)
2023-06-06 20:13:11 +02:00
2023-06-30 18:54:33 +02:00
Endlich ruhig schlafen bei der Frontend-Entwicklung. 😴
2023-06-06 20:13:11 +02:00
---
2023-06-30 18:54:33 +02:00
# Was ist Typescript?
2023-06-06 20:13:11 +02:00
2023-07-03 15:16:41 +02:00
* Erweitert Javascript
* Fügt Typeninformationen in die Sprache ein
* Daten und Datenfluss wird besser beschrieben
2023-06-06 20:13:11 +02:00
---
2023-06-30 18:54:33 +02:00
# Fehlt es Javascript an Types?
2023-06-06 20:13:11 +02:00
2023-06-30 18:54:33 +02:00
```js
var data; // Was darf hier rein?
2023-06-06 23:55:46 +02:00
```
2023-06-30 18:54:33 +02:00
---
# 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
2023-07-02 19:41:56 +02:00
*
2023-06-30 18:54:33 +02:00
* @param {GraphOptions} options
*/
export function drawGraph(data, options = {}) {
[...Implementierungsdetail]
}
// your-module.js
drawGraph(
2023-07-02 19:41:56 +02:00
data,
2023-06-30 18:54:33 +02:00
{
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)
---
2023-07-03 14:04:17 +02:00
## Integration in bestehende Projekte
![bg left:50% 100%](img/typescript-integration.png)
![w:150](img/squarehole.png)
---
2023-06-30 18:54:33 +02:00
## 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
2023-07-02 19:41:56 +02:00
* => Nutzung von Typescript verliert an Wert
2023-06-30 18:54:33 +02:00
* Alle Abhängigkeiten direkt nach Typescript konvertieren
2023-07-02 19:41:56 +02:00
* Nicht immer machbar
2023-06-30 18:54:33 +02:00
* 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
---
2023-07-02 09:41:35 +02:00
## 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 = {
2023-07-02 19:41:56 +02:00
anzahlRaeder: number,
hubraum?: Kubikmeter,
2023-07-02 09:41:35 +02:00
};
```
---
## Nur optionale Properties
```ts
type Vehicle = Partial<{
2023-07-02 19:41:56 +02:00
anzahlRaeder: number,
hubraum: Kubikmeter,
anzahlKufen: number,
2023-07-02 09:41:35 +02:00
}>;
```
![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<{
2023-07-02 19:41:56 +02:00
anzahlRaeder: number,
hubraum: Kubikmeter,
anzahlKufen: number,
2023-07-02 09:41:35 +02:00
}>;
const helikopter: Vehicle = {
anzahlKufen: 2,
};
```
---
## Nur optionale Properties
```ts
type Vehicle = Partial<{
2023-07-02 19:41:56 +02:00
anzahlRaeder: number,
hubraum: Kubikmeter,
anzahlKufen: number,
2023-07-02 09:41:35 +02:00
}>;
const helikopter: Vehicle = {
anzahlKufen: 2,
};
const bobbyCar: Vehicle = {
anzahlRaeder: 4,
};
```
---
## Nur optionale Properties
```ts
type Vehicle = Partial<{
2023-07-02 19:41:56 +02:00
anzahlRaeder: number,
hubraum: Kubikmeter,
anzahlKufen: number,
2023-07-02 09:41:35 +02:00
}>;
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 😎
```
2023-07-02 19:41:56 +02:00
---
## 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
2023-07-03 14:04:17 +02:00
![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
2023-07-03 15:16:41 +02:00
```ts
function inferBroken(str: string) {
if (['Bernd', 'Margeret'].includes(str)) {
const narrowed: 'Bernd'|'Margeret' = str;
}
}
```
---
## Deduktionsschwächen
2023-07-03 14:04:17 +02:00
```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)