15 KiB
15 KiB
theme, _class, paginate, backgroundColor, color
| theme | _class | paginate | backgroundColor | color |
|---|---|---|---|---|
| gaia | lead | true |
Typescript
Endlich ruhig schlafen bei der Frontend-Entwicklung. 😴
Was ist Typescript?
- Erweitert Javascript
- Fügt Typeninformationen in die Sprache ein
- Daten und Datenfluss wird besser beschrieben
Fehlt es Javascript an Types?
var data; // Was darf hier rein?
Fehlt es Javascript an Types?
> var data = "some string";
> console.log(data);
"some string"
> data = 15; // Why not both?
> console.log(data);
15
Jetzt mit Types!
Jetzt mit Types!
var data: string = "some string";
console.log(data);
data = 15; // ❌ Error
console.log(data);
Transparenz von Schnittstellen
Was tut diese Library?
// @iserv/graph.js
export function drawGraph(data, options = {}) {
[... Implementierungsdetails]
}
Transparenz von Schnittstellen
Was tut diese Library?
// @iserv/graph.js
export function drawGraph(data, options = {}) {
[... Implementierungsdetails]
}
Besser: Wie benutze ich diese Library?
Transparenz von Schnittstellen
// @iserv/graph.js
export function drawGraph(data, options = {}) {
[... Implementierungsdetails]
}
// your-module.js
drawGraph([{
x: 2,
y: 5,
}, {
x: 3,
y: 7,
}]);
Transparenz von Schnittstellen
// @iserv/graph.js
export function drawGraph(data, options = {}) {
[... Implementierungsdetails]
}
// your-module.js
drawGraph([
5,
7
]);
Transparenz von Schnittstellen
// @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!
// @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?
- JSDoc kann lügen
- Wird nicht automatisch überprüft
Typescript aufsetzen
- 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
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
Kompilieren
tsc [<file>]
Kompilieren
// index.ts
let variable: number = 5;
variable = 10;
<one tsc later>
// index.js
"use strict";
let variable = 5;
variable = 10;
Integration in Webpack-Projekten
Integration in Webpack-Projekten
Integration in Webpack-Projekten
npm install --save-dev typescript ts-loader
^^^^^^^^^
Integration in Webpack-Projekten
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
// [...]
};
Integration in bestehende Projekte
Integration in bestehende Projekte
Typescript in Javascript?
- TS in JS integrieren geht ohne Probleme
-
// 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
-
// 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
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
Schnittstellen typisieren
// 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?
const workerBee = {
role: 'worker',
name: 'Larry',
age: 15,
};
Objekte
const workerBee: Bee = {
role: 'worker',
name: 'Larry',
age: 15,
};
type Bee = {
role: string,
name: string,
age: number,
};
Semantische Typen
const workerBee: Bee = {
role: 'worker',
name: 'Larry',
age: 15,
};
type Bee = {
role: BeeRole,
name: string,
age: Months,
};
type BeeRole = 'worker'|'queen'|'guard';
type Months = number;
Dictionaries
const userNames = {
'652a4707-3043-4ca9-b417-24feac0f5953': 'Bernhard',
'017daef7-e1f1-4160-81ff-e5b17e86fc6b': 'Frieda',
};
Dictionaries
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)
const userNames: UserNameDictionary = {
'652a4707-3043-4ca9-b417-24feac0f5953': 'Bernhard',
'017daef7-e1f1-4160-81ff-e5b17e86fc6b': 'Frieda',
};
type UserId = string;
type UserNameDictionary = {
[UserId]: string;
};
Optionale Properties
const lkw = {
anzahlRaeder: 18,
hubraum: 25,
};
const bobbycar = {
anzahlRaeder: 4,
};
Optionale Properties
const lkw: Car = {
anzahlRaeder: 18,
hubraum: 25,
};
const bobbycar: Car = {
anzahlRaeder: 4,
};
type Kubikmeter = number;
type Car = {
anzahlRaeder: number,
hubraum?: Kubikmeter,
};
Nur optionale Properties
type Vehicle = Partial<{
anzahlRaeder: number,
hubraum: Kubikmeter,
anzahlKufen: number,
}>;
Nur optionale Properties
type Vehicle = Partial<{
anzahlRaeder: number,
hubraum: Kubikmeter,
anzahlKufen: number,
}>;
const helikopter: Vehicle = {
anzahlKufen: 2,
};
Nur optionale Properties
type Vehicle = Partial<{
anzahlRaeder: number,
hubraum: Kubikmeter,
anzahlKufen: number,
}>;
const helikopter: Vehicle = {
anzahlKufen: 2,
};
const bobbyCar: Vehicle = {
anzahlRaeder: 4,
};
Nur optionale Properties
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
// @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
// @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',
};
// consumer.ts
import {colors} from '@iserv/colors.ts';
colors = {}; // verboten wegen const
Readonly
// @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',
};
// consumer.ts
import {colors} from '@iserv/colors.ts';
colors = {}; // verboten wegen const
colors.BLUE = colors.RED; // erlaubt 😢
Readonly
// @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',
};
// consumer.ts
import {colors} from '@iserv/colors.ts';
colors = {}; // verboten wegen const
colors.BLUE = colors.RED; // verbiet 😎
Typen kombinieren
type Renderable = {
render: () => void,
}
type Updateable = {
update: () => void,
}
const gameObject = {
render: () => {},
update: () => {},
};
Typen kombinieren - Union
type Renderable = {
render: () => void,
}
type Updateable = {
update: () => void,
}
type GameObject = Renderable & Updateable;
const gameObject: GameObject = {
render: () => {},
update: () => {},
};
Typen kombinieren
type NetworkPacket = {
data: Record<string, string>,
msg: string,
};
type Exception = {
trace: string[],
msg: string,
}
Typen kombinieren - Intersection
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
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
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
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
-
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
Type narrowing
const ducks: unknown = await fetch('https://api.ducks.com/sorts.json');
Type narrowing
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
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
}
Automatische Type-Guard Generierung
github.com/rhys-vdw/ts-auto-guard- Generiert Type-Guards automatisch
-
/** @see {isDuckSort} ts-auto-guard:type-guard */ export type DuckSort = {/** ... */}; npx ts-auto-guard
ts-auto-guard Beispiel
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
const config = readFromFile('config.yml') as Config;
as-Operator ist der Holzhammer- Typescript glaubt dir alles
- Führt potenzielle Bugs ein
as-Operator
const number = {name: 'blarg'} as number;
Deduktionsschwächen
function inferBroken(str: string) {
if (['Bernd', 'Margeret'].includes(str)) {
const narrowed: 'Bernd'|'Margeret' = str;
}
}
Deduktionsschwächen
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
function inferBroken(str: string) {
if (['Bernd', 'Margeret'].includes(str)) {
const narrowed = str as 'Bernd'|'Margeret';
}
}
Deduktionsschwächen - Do NOT
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
















