This commit is contained in:
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);
|
||||
}
|
||||
})();
|
||||
|
||||
+37
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user