Zum Inhalt springen

1.6: In TypeScript umwandeln

Gegeben sei das untenstehende node-Programm in JavaScript. Analysiere den Quellcode und wandle ihn anschliessend in TypeScript um.

Analyse

  1. Kopiere den JavaScript Code in eine neue Datei example.js im Ordner 1-6-umwandeln.
  2. Sieh dir den Quellcode genau an. Überlege, was das Programm genau macht.
  3. Fülle die <???>-Lücken in den Kommentaren mit einer kurzen Beschreibung des jeweiligen Codeblocks.
  4. Führe das Programm aus mit node 1-6-umwandeln/example.js.
  5. Überlege, was der Code für Probleme aufweist, die mit TypeScript gelöst werden könnten.

Umwandlung zu TypeScript

  1. Kopiere die Datei und benenne die Kopie um in example.ts.
  2. Sieh dir die Datei im Editor an. Was für Fehler werden angezeigt?
  3. Versuche die Datei trotzdem zu kompilieren mit tsc --target esnext --module nodenext example.ts und vergleiche die Ursprungsdatei mit dem Resultat.
  4. Führe das Programm aus mit node ./example.js.

Anreicherung mit Typen

  1. Erstelle die Typ-Definitionen anhand der Dokumentation

  2. Finde heraus wie ein User-Objekt aussieht und erstelle dafür einen entsprechenden type

  3. Finde heraus wie ein Post-Objekt aussieht und erstelle dafür einen entsprechenden type

  4. Finde heraus wie ein Comment-Objekt aussieht und erstelle dafür einen entsprechenden type

  5. Reichere die Funktionen mit den typisierten Signaturen an und verwende dafür die erstellten Typ-Definitionen. Asynchrone Funktionen verwenden den Rückgabe-Typ Promise<>.

  6. Ergänze die Signatur von fetchUsers mit dem Rückgabe-Typ Promise<User[]>

  7. Ergänze die Signatur von fetchPosts mit einem Rückgabe- und Parameter-Typ

  8. Ergänze die Signatur von fetchComments mit einem Rückgabe- und Parameter-Typ

Abfangen von Spezialfällen

  1. Stelle sicher, dass alle Funktionen die korrekten, typisierten Werte zurückgeben
  2. Fange alle Spezialfälle ab, indem du zusätzliche Verzweigungen und Prüfungen im Code implementierst
  3. Stelle sicher, dass dein Code auch mit --strict kompiliert, also tsc --strict --target esnext --module nodenext example.ts
  4. Vergleiche nochmals den kompilierten JavaScript Code mit der Ursprungsdatei. Was hat sich verändert?

Weitere Verbesserungen (optional)

  1. Nimm weitere Verbesserungen am Code vor, begründe jeweils, warum du etwas so gemacht hast
// `readline` und `cli` werden benötigt, um Eingaben vom Benutszer abzufragen
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const cli = readline.createInterface({ input, output });
// <???>
async function fetchUsers() {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
return res.json();
}
// <???>
async function fetchPosts(userId) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
return res.json();
}
// <???>
async function fetchComments(postId) {
const res = await fetch(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`);
return res.json();
}
// <???>
async function askText(question) {
const answer = await cli.question(question);
return answer.trim();
}
// <???>
async function askNumber(question) {
const answer = await cli.question(question);
return parseInt(answer);
}
// <???>
async function askYesNo(question) {
const answer = await cli.question(question);
return answer.trim().toLowerCase() === 'y';
}
async function main() {
while (true) {
// <???>
const users = await fetchUsers();
for (const user of users) {
console.log(`@${user.username}: ${user.name}`);
}
// <???>
const username = (await askText("\nEnter a username: @"));
const user = users.find(user => user.username.toLowerCase() === username.toLowerCase());
// <???>
console.log(`\n@${user.username} Posts:`);
console.log(`Name: ${user.name}`);
console.log(`Email: ${user.email}`);
console.log(`Address: ${user.address.street}, ${user.address.suite}, ${user.address.city}, ${user.address.zipcode}`);
console.log(`Location: 📌 Lat ${user.address.geo.lat}, Lng ${user.address.geo.lng}`);
// <???>
const posts = await fetchPosts(user.id);
for (const post of posts) {
console.log(`${post.id}: ${post.title}`);
}
// <???>
const postId = await askNumber("\nEnter a post ID: ");
const comments = await fetchComments(postId);
for (const comment of comments) {
console.log(`💬 ${comment.email}: ${comment.body}\n`);
}
// <???>
const startOver = await askYesNo("\nDo you want to start over? (y/n): ");
if(startOver == true) {
console.log("\n\nStarting over...\n");
} else {
console.log("\n\nShutting down...\n");
break;
}
}
// `cli` muss am Programmende wieder geschlossen werden
cli.close();
}
main();

Lösungsvorschlag

// `readline` und `cli` werden benötigt, um Eingaben vom Benutzer abzufragen
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const cli = readline.createInterface({ input, output });
type User = {
id: number;
name: string;
username: string;
email: string;
address: {
street: string;
suite: string;
city: string;
zipcode: string;
geo: {
lat: string;
lng: string;
};
};
phone: string;
website: string;
company: {
name: string;
catchPhrase: string;
bs: string;
};
};
type Post = {
userId: number;
id: number;
title: string;
body: string;
};
type Comment = {
postId: number;
id: number;
name: string;
email: string;
body: string;
};
// Ruft die Liste der Benutzer von der API ab
async function fetchUsers(): Promise<User[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
return res.json() as unknown as User[];
}
// Ruft die Liste der Beiträge eines bestimmten Benutzers von der API ab
async function fetchPosts(userId: number): Promise<Post[]> {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
return res.json() as unknown as Post[];
}
// Ruft die Liste der Kommentare zu einem bestimmten Beitrag von der API ab
async function fetchComments(postId: number): Promise<Comment[]> {
const res = await fetch(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`);
return res.json() as unknown as Comment[];
}
// Fragt den Benutzer nach einer Texteingabe
async function askText(question: string): Promise<string> {
const answer: string = await cli.question(question);
return answer.trim();
}
// Fragt den Benutzer nach einer Zahleneingabe
async function askNumber(question: string): Promise<number> {
const answer: string = await cli.question(question);
const parsed = parseInt(answer, 10);
if (isNaN(parsed)) {
console.log("❌ Invalid number input. Please try again.");
return askNumber(question); // Call the function again
}
return parsed;
}
// Fragt den Benutzer nach einer Ja/Nein-Antwort
async function askYesNo(question: string): Promise<boolean> {
const answer: string = await cli.question(question);
const normalized = answer.trim().toLowerCase();
if (normalized !== 'y' && normalized !== 'n') {
console.log("❌ Invalid input. Please enter 'y' or 'n'.");
return askYesNo(question); // Call the function again
}
return normalized === 'y';
}
async function main(): Promise<void> {
while (true) {
// Ruft die Benutzerliste ab und zeigt sie an
const users = await fetchUsers();
for (const user of users) {
console.log(`@${user.username}: ${user.name}`);
}
// Fragt den Benutzer nach einem Benutzernamen und sucht den entsprechenden Benutzer
const username: string = await askText("\nEnter a username: @");
const user: User | undefined = users.find(user => user.username.toLowerCase() === username.toLowerCase());
if (!user) {
console.log("❌ User not found. Please try again.");
continue;
}
// Zeigt Informationen über den ausgewählten Benutzer an
console.log(`\n@${user.username} Posts:`);
console.log(`Name: ${user.name}`);
console.log(`Email: ${user.email}`);
console.log(`Address: ${user.address.street}, ${user.address.suite}, ${user.address.city}, ${user.address.zipcode}`);
console.log(`Location: 📌 Lat ${user.address.geo.lat}, Lng ${user.address.geo.lng}`);
// Ruft die Beiträge des Benutzers ab und zeigt sie an
const posts = await fetchPosts(user.id);
for (const post of posts) {
console.log(`${post.id}: ${post.title}`);
}
// Fragt den Benutzer nach einer Beitrags-ID und zeigt die zugehörigen Kommentare an
const postId: number = await askNumber("\nEnter a post ID: ");
const post: Post | undefined = posts.find(post => post.id === postId);
if (!post) {
console.log("❌ Post not found. Please try again.");
continue;
}
const comments = await fetchComments(postId);
for (const comment of comments) {
console.log(`💬 ${comment.email}: ${comment.body}\n`);
}
// Fragt den Benutzer, ob er von vorne beginnen möchte
const startOver: boolean = await askYesNo("\nDo you want to start over? (y/n): ");
if (startOver) {
console.log("\n\nStarting over...\n");
} else {
console.log("\n\nShutting down...\n");
break;
}
}
// `cli` muss am Programmende wieder geschlossen werden
cli.close();
}
main();