This commit is contained in:
Philippe Torrel
2026-10-19 13:18:13 +02:00
parent f474ba8ea0
commit 73de568a05
9 changed files with 342 additions and 4 deletions
Binary file not shown.
@@ -1,5 +1,30 @@
(() => {
// src/04_in-operator.ts
{
const operate = (vehicle) => {
if ("drive" in vehicle) {
vehicle.drive();
} else {
vehicle.sail();
}
};
const car = { drive: () => console.log("I'm driving") };
const sealander = { sail: () => console.log("I'm sailing") };
operate(car);
operate(sealander);
const move = (entity) => {
if ("walk" in entity) {
entity.walk?.();
} else if ("fly" in entity && entity.fly) {
entity.fly();
} else if ("bark" in entity) {
entity.bark();
}
};
const dog = { bark: () => console.log("Woof") };
const bird = { fly: () => console.log("Flying") };
const robot = { walk: () => console.log("Walking") };
const optimus = { fly: () => console.log("Flying Robo") };
move(robot);
}
})();
@@ -0,0 +1,37 @@
(() => {
// src/05_kontrollflussanalyse.ts
{
const processData = (data) => {
if (typeof data === "boolean") {
console.log("Boolean value:", data);
} else if (typeof data === "string") {
console.log("String value:", data.toUpperCase());
} else {
console.log("Number value:", data.toFixed(2));
}
};
processData(true);
processData("hello");
processData(123);
const checkValue = (value) => {
if (typeof value === "string") {
return `String: ${value}`;
}
return `Number: ${value.toFixed(2)}`;
};
console.log(checkValue("hello"));
console.log(checkValue(123));
const describeInput = (input) => {
if (input === null) {
return "No input provided.";
}
if (typeof input === "string") {
return `String input: ${input.toUpperCase()}`;
}
return `Number input: ${input.toFixed(2)}`;
};
console.log(describeInput("hello"));
console.log(describeInput(123));
console.log(describeInput(null));
}
})();
@@ -0,0 +1,28 @@
(() => {
// src/06_type-predicate.ts
{
const isFish = (pet2) => {
return Object.hasOwn(pet2, "swim");
};
const handlePet = (pet2) => {
if (isFish(pet2)) {
pet2.swim();
} else {
pet2.fly();
}
};
const pet = { swim: () => console.log("Is swimming") };
const amsel = { fly: () => console.log("Is flying") };
handlePet(pet);
handlePet(amsel);
const zoo = [
{ swim: () => console.log("Fish swimming") },
{ fly: () => console.log("Bird flying") },
{ fly: () => console.log("Another Bird flying") },
{ fly: () => console.log("Bird3 flying") },
{ swim: () => console.log("Another Fish flying") }
];
const fishes = zoo.filter((pet2) => isFish(pet2));
fishes.forEach((fish) => fish.swim());
}
})();
@@ -6,9 +6,12 @@
<title>TypeScript - Grundlagen</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" />
<script src="assets/js/01_narrowing.js" defer></script>
<!-- <script src="assets/js/01_narrowing.js" defer></script>
<script src="assets/js/02_truthiness.js" defer></script>
<script src="assets/js/03_equality.js" defer></script>
<script src="assets/js/03_equality.js" defer></script>
<script src="assets/js/04_in-operator.js" defer></script>
<script src="assets/js/05_kontrollflussanalyse.js" defer></script>-->
<script src="assets/js/06_type-predicate.js" defer></script>
</head>
<body>
<main>
@@ -0,0 +1,86 @@
## `in`-Operator vs Type Predicate
Der `in`-Operator fungiert in TypeScript als **eingebauter Type Guard** und bewirkt ein automatisches **Type Narrowing**, verhält sich im Detail jedoch flexibler und nachsichtiger als ein handgeschriebenes Type Predicate (`pet is Fish`).
---
### Gemeinsamkeit
Beide Mechanismen verengen eine Union:
```typescript
// Variante A: Direkt mit `in`
if ('swim' in pet) {
pet.swim(); // TS verengt pet zu Fish
} else {
pet.fly(); // TS verengt pet zu Bird
}
// Variante B: Mit Type Predicate
if (isFish(pet)) {
pet.swim(); // TS verengt pet zu Fish
} else {
pet.fly(); // TS verengt pet zu Bird
}
```
In einfachen Unions mit distinkten Properties ist der praktische Effekt im `if`/`else`-Block identisch.
---
### Wesentliche Unterschiede
| Kriterium | `in`-Operator | Eigene Predicate-Funktion (`pet is Fish`) |
| --- | --- | --- |
| **Prüflogik** | Prüft ausschließlich die **Existenz des Keys** (auch in der Prototypenkette). | Beliebige Laufzeit-Validierung (Werte, Typen, verschachtelte Felder, Regex etc.). |
| **Wiederverwendbarkeit** | Nur inline an der Stelle der Prüfung. | Modular exportierbar und z. B. in `.filter(isFish)` nutzbar. |
| **Typsicherheit** | **Vollständig vom Compiler verifiziert.** TS schaut sich die Typdefinitionen an. | **Verhält sich wie ein Type Cast (`as`).** TS vertraut blind dem Rückgabewert `pet is Fish`, selbst wenn die Laufzeitprüfung fehlerhaft ist. |
| **Umgang mit unbekannten Typen** | Verengt `object` oder `unknown` zu `Record<"swim", unknown> & object`. | Verengt direkt auf den Zieltyp `Fish`. |
---
### 1. Type Predicates sind "Vertrauenssache" (Compiler prüft nicht den Inhalt)
Wenn du ein Prädikat schreibst:
```typescript
const isFish = (pet: Fish | Bird): pet is Fish => {
return true; // Bug!
};
if (isFish(pet)) {
pet.swim(); // TS meckert nicht, crasht aber zur Laufzeit
}
```
TypeScript überprüft nicht, ob die Logik im Body von `isFish` tatsächlich beweist, dass es sich um einen Fisch handelt. Beim `in`-Operator hingegen wertet TypeScript die tatsächlichen Eigenschaften der beteiligten Typen aus.
---
### 2. Wiederverwendbarkeit in Array-Methoden
Ein großer Vorteil des Type Predicates ist die Verwendung in Higher-Order Functions:
```typescript
const pets: (Fish | Bird)[] = [/* ... */];
// Funktioniert nur mit Predicate:
const fishes: Fish[] = pets.filter(isFish);
// Funktioniert nicht direkt mit 'in':
// pets.filter(pet => 'swim' in pet) liefert weiterhin (Fish | Bird)[]
```
---
### 3. Prototypenkette vs. Own Properties
In deinem Code-Kommentar steht:
* `Object.hasOwn(pet, 'swim')` bzw. `pet.hasOwnProperty('swim')` prüft nur **eigene Eigenschaften**.
* `'swim' in pet` prüft auch Methoden, die auf dem **Prototyp** definiert sind (z. B. wenn `Fish` eine Klasse ist).
Wenn `Fish` als Klasse deklariert ist, liegt `swim()` auf `Fish.prototype`. In diesem Fall würde `Object.hasOwn(pet, 'swim')` zur Laufzeit `false` liefern, während `'swim' in pet` korrekt `true` ergibt.
@@ -1,5 +1,55 @@
// In Operator - wird verwendet, um zu bestimmen, ob ein Objekt ein bestimmtes Property hat.
{
type Car = { drive: () => void };
type Boat = { sail: () => void };
type Car = {
drive: () => void;
// drive():void
};
type Boat = {
sail: () => void;
// sail(): void
};
const operate = (vehicle: Car | Boat) => {
// Property 'drive' does not exist on type 'Boat'.
// vehicle.drive(); // Property 'drive' does not exist on type 'Car | Boat'.
// Object.hasOwn() führt kein narrowing aus | grenzt nicht den Datentyp ein
// if (Object.hasOwn(vehicle, 'drive')) {
if ('drive' in vehicle) {
vehicle.drive();
} else {
vehicle.sail();
}
};
const car: Car = { drive: () => console.log("I'm driving") };
const sealander: Boat = { sail: () => console.log("I'm sailing") };
operate(car);
operate(sealander);
// -------
type Dog = { bark: () => void };
type Bird = { fly: () => void };
type Robot = { walk?: () => void; fly?: () => void | undefined };
const move = (entity: Dog | Bird | Robot) => {
if ('walk' in entity) {
entity.walk?.();
} else if ('fly' in entity && entity.fly) {
entity.fly();
} else if ('bark' in entity) {
entity.bark();
}
};
const dog: Dog = { bark: () => console.log('Woof') };
const bird: Bird = { fly: () => console.log('Flying') };
const robot: Robot = { walk: () => console.log('Walking') };
const optimus: Robot = { fly: () => console.log('Flying Robo') };
// move(dog);
// move(bird);
move(robot);
// move(optimus);
}
@@ -0,0 +1,52 @@
// Kontrollflussanalyse
/*
In TypeScript wird der Kontrollfluss analysiert, um zu bestimmen, welche Typen eine Variable haben kann. Dies wird auch als "Type Narrowing" bezeichnet.
Type Narrowing wird verwendet, um den Typ einer Variablen zu beschränken, wenn bestimmte Bedingungen erfüllt sind.
*/
{
const processData = (data: string | number | boolean): void => {
if (typeof data === 'boolean') {
console.log('Boolean value:', data);
} else if (typeof data === 'string') {
console.log('String value:', data.toUpperCase());
} else {
console.log('Number value:', data.toFixed(2));
}
};
processData(true);
processData('hello');
processData(123);
// Kontrollfluss mit Early Returns
const checkValue = (value: string | number): string => {
// Guard
if (typeof value === 'string') {
return `String: ${value}`; // early return bei Narrowing
}
return `Number: ${value.toFixed(2)}`;
};
console.log(checkValue('hello'));
console.log(checkValue(123));
// ---
const describeInput = (input: string | number | null): string => {
if (input === null) {
return 'No input provided.';
}
if (typeof input === 'string') {
return `String input: ${input.toUpperCase()}`;
}
return `Number input: ${input.toFixed(2)}`;
};
console.log(describeInput('hello'));
console.log(describeInput(123));
console.log(describeInput(null));
}
@@ -0,0 +1,57 @@
// Type Predicate
/*
Ein Type Predicate ist eine besondere Art von Funktion, die einen booleschen Wert zurückgibt, der angibt, ob eine Variable einem bestimmten Typ entspricht.
WICHTIG: "Außerdem grenzt es den Typ der Variablen auf der Grundlage des Ergebnisses der Prüfung ein."
*/
{
type Fish = { swim: () => void };
type Bird = { fly: () => void };
// "pet is Fish" <- Type predicate
const isFish = (pet: Fish | Bird): pet is Fish => {
return Object.hasOwn(pet, 'swim');
// return pet.hasOwnProperty('swim');
// return typeof (pet as Fish).swim !== 'undefined';
// return 'swim' in pet; // in Operator führt Type-Narrowing durch
};
const handlePet = (pet: Fish | Bird) => {
// Property 'swim' does not exist on type 'Fish | Bird'.
// Property 'swim' does not exist on type 'Bird'.
// pet.swim();
// if ('swim' in pet) {
if (isFish(pet)) {
pet.swim(); // TypeScript knows pet is a Fish Object
} else {
pet.fly(); // TypeScript knows pet is a Bird Object
}
};
const pet: Fish | Bird = { swim: () => console.log('Is swimming') };
const amsel: Bird = { fly: () => console.log('Is flying') };
handlePet(pet);
handlePet(amsel);
//------
// const zoo: (Fish | Bird)[] = [
// { swim: () => console.log('Fish swimming') },
// { fly: () => console.log('Bird flying') },
// { swim: () => console.log('Another Fish flying') },
// ];
const zoo: Array<Fish | Bird> = [
{ swim: () => console.log('Fish swimming') },
{ fly: () => console.log('Bird flying') },
{ fly: () => console.log('Another Bird flying') },
{ fly: () => console.log('Bird3 flying') },
{ swim: () => console.log('Another Fish flying') },
];
const fishes = zoo.filter((pet) => isFish(pet));
fishes.forEach((fish) => fish.swim());
}