Ελληνικά

Εξερευνήστε τους literal τύπους της TypeScript, ένα ισχυρό χαρακτηριστικό για την επιβολή αυστηρών περιορισμών τιμών, βελτιώνοντας την καθαρότητα του κώδικα και αποτρέποντας σφάλματα. Μάθετε με πρακτικά παραδείγματα και προηγμένες τεχνικές.

TypeScript Literal Types: Κατακτώντας τους Περιορισμούς Ακριβών Τιμών

Η TypeScript, ένα υπερσύνολο της JavaScript, φέρνει τη στατική τυποποίηση στον δυναμικό κόσμο της ανάπτυξης web. Ένα από τα πιο ισχυρά χαρακτηριστικά της είναι η έννοια των literal τύπων. Οι literal τύποι σας επιτρέπουν να καθορίσετε την ακριβή τιμή που μπορεί να έχει μια μεταβλητή ή μια ιδιότητα, παρέχοντας ενισχυμένη ασφάλεια τύπων και αποτρέποντας απροσδόκητα σφάλματα. Αυτό το άρθρο θα εξερευνήσει τους literal τύπους σε βάθος, καλύπτοντας τη σύνταξη, τη χρήση και τα οφέλη τους με πρακτικά παραδείγματα.

Τι είναι οι Literal Τύποι;

Σε αντίθεση με τους παραδοσιακούς τύπους όπως string, number, ή boolean, οι literal τύποι δεν αντιπροσωπεύουν μια ευρεία κατηγορία τιμών. Αντ' αυτού, αντιπροσωπεύουν συγκεκριμένες, σταθερές τιμές. Η TypeScript υποστηρίζει τρία είδη literal τύπων:

Χρησιμοποιώντας τους literal τύπους, μπορείτε να δημιουργήσετε πιο ακριβείς ορισμούς τύπων που αντικατοπτρίζουν τους πραγματικούς περιορισμούς των δεδομένων σας, οδηγώντας σε πιο στιβαρό και συντηρήσιμο κώδικα.

String Literal Types

Οι string literal τύποι είναι το πιο συχνά χρησιμοποιούμενο είδος literal. Σας επιτρέπουν να καθορίσετε ότι μια μεταβλητή ή μια ιδιότητα μπορεί να περιέχει μόνο μία από ένα προκαθορισμένο σύνολο τιμών string.

Βασική Σύνταξη

Η σύνταξη για τον ορισμό ενός string literal τύπου είναι απλή:


type AllowedValues = "value1" | "value2" | "value3";

Αυτό ορίζει έναν τύπο με όνομα AllowedValues που μπορεί να περιέχει μόνο τα strings "value1", "value2", ή "value3".

Πρακτικά Παραδείγματα

1. Ορισμός μιας παλέτας χρωμάτων:

Φανταστείτε ότι δημιουργείτε μια βιβλιοθήκη UI και θέλετε να διασφαλίσετε ότι οι χρήστες μπορούν να καθορίσουν μόνο χρώματα από μια προκαθορισμένη παλέτα:


type Color = "red" | "green" | "blue" | "yellow";

function paintElement(element: HTMLElement, color: Color) {
  element.style.backgroundColor = color;
}

paintElement(document.getElementById("myElement")!, "red"); // Έγκυρο
paintElement(document.getElementById("myElement")!, "purple"); // Σφάλμα: Argument of type '"purple"' is not assignable to parameter of type 'Color'.

Αυτό το παράδειγμα δείχνει πώς οι string literal τύποι μπορούν να επιβάλουν ένα αυστηρό σύνολο επιτρεπόμενων τιμών, αποτρέποντας τους προγραμματιστές από το να χρησιμοποιήσουν κατά λάθος μη έγκυρα χρώματα.

2. Ορισμός API Endpoints:

Όταν εργάζεστε με APIs, συχνά χρειάζεται να καθορίσετε τα επιτρεπόμενα endpoints. Οι string literal τύποι μπορούν να βοηθήσουν στην επιβολή αυτού:


type APIEndpoint = "/users" | "/posts" | "/comments";

function fetchData(endpoint: APIEndpoint) {
  // ... υλοποίηση για την ανάκτηση δεδομένων από το καθορισμένο endpoint
  console.log(`Fetching data from ${endpoint}`);
}

fetchData("/users"); // Έγκυρο
fetchData("/products"); // Σφάλμα: Argument of type '"/products"' is not assignable to parameter of type 'APIEndpoint'.

Αυτό το παράδειγμα διασφαλίζει ότι η συνάρτηση fetchData μπορεί να κληθεί μόνο με έγκυρα API endpoints, μειώνοντας τον κίνδυνο σφαλμάτων που προκαλούνται από τυπογραφικά λάθη ή λανθασμένα ονόματα endpoint.

3. Διαχείριση διαφορετικών γλωσσών (Internationalization - i18n):

Σε παγκόσμιες εφαρμογές, μπορεί να χρειαστεί να διαχειριστείτε διαφορετικές γλώσσες. Μπορείτε να χρησιμοποιήσετε string literal τύπους για να διασφαλίσετε ότι η εφαρμογή σας υποστηρίζει μόνο τις καθορισμένες γλώσσες:


type Language = "en" | "es" | "fr" | "de" | "zh";

function translate(text: string, language: Language): string {
  // ... υλοποίηση για τη μετάφραση του κειμένου στην καθορισμένη γλώσσα
  console.log(`Translating '${text}' to ${language}`);
  return "Translated text"; // Placeholder
}

translate("Hello", "en"); // Έγκυρο
translate("Hello", "ja"); // Σφάλμα: Argument of type '"ja"' is not assignable to parameter of type 'Language'.

Αυτό το παράδειγμα δείχνει πώς να διασφαλίσετε ότι χρησιμοποιούνται μόνο υποστηριζόμενες γλώσσες εντός της εφαρμογής σας.

Number Literal Types

Οι number literal τύποι σας επιτρέπουν να καθορίσετε ότι μια μεταβλητή ή μια ιδιότητα μπορεί να περιέχει μόνο μια συγκεκριμένη αριθμητική τιμή.

Βασική Σύνταξη

Η σύνταξη για τον ορισμό ενός number literal τύπου είναι παρόμοια με τους string literal τύπους:


type StatusCode = 200 | 404 | 500;

Αυτό ορίζει έναν τύπο με όνομα StatusCode που μπορεί να περιέχει μόνο τους αριθμούς 200, 404, ή 500.

Πρακτικά Παραδείγματα

1. Ορισμός κωδικών κατάστασης HTTP:

Μπορείτε να χρησιμοποιήσετε number literal τύπους για να αναπαραστήσετε κωδικούς κατάστασης HTTP, διασφαλίζοντας ότι χρησιμοποιούνται μόνο έγκυροι κωδικοί στην εφαρμογή σας:


type HTTPStatus = 200 | 400 | 401 | 403 | 404 | 500;

function handleResponse(status: HTTPStatus) {
  switch (status) {
    case 200:
      console.log("Success!");
      break;
    case 400:
      console.log("Bad Request");
      break;
    // ... άλλες περιπτώσεις
    default:
      console.log("Unknown Status");
  }
}

handleResponse(200); // Έγκυρο
handleResponse(600); // Σφάλμα: Argument of type '600' is not assignable to parameter of type 'HTTPStatus'.

Αυτό το παράδειγμα επιβάλλει τη χρήση έγκυρων κωδικών κατάστασης HTTP, αποτρέποντας σφάλματα που προκαλούνται από τη χρήση λανθασμένων ή μη τυποποιημένων κωδικών.

2. Αναπαράσταση σταθερών επιλογών:

Μπορείτε να χρησιμοποιήσετε number literal τύπους για να αναπαραστήσετε σταθερές επιλογές σε ένα αντικείμενο διαμόρφωσης:


type RetryAttempts = 1 | 3 | 5;

interface Config {
  retryAttempts: RetryAttempts;
}

const config1: Config = { retryAttempts: 3 }; // Έγκυρο
const config2: Config = { retryAttempts: 7 }; // Σφάλμα: Type '{ retryAttempts: 7; }' is not assignable to type 'Config'.

Αυτό το παράδειγμα περιορίζει τις πιθανές τιμές για το retryAttempts σε ένα συγκεκριμένο σύνολο, βελτιώνοντας την καθαρότητα και την αξιοπιστία της διαμόρφωσής σας.

Boolean Literal Types

Οι boolean literal τύποι αντιπροσωπεύουν τις συγκεκριμένες τιμές true ή false. Ενώ μπορεί να φαίνονται λιγότερο ευέλικτοι από τους string ή number literal τύπους, μπορούν να είναι χρήσιμοι σε συγκεκριμένα σενάρια.

Βασική Σύνταξη

Η σύνταξη για τον ορισμό ενός boolean literal τύπου είναι:


type IsEnabled = true | false;

Ωστόσο, η απευθείας χρήση του true | false είναι πλεονασμός επειδή είναι ισοδύναμο με τον τύπο boolean. Οι boolean literal τύποι είναι πιο χρήσιμοι όταν συνδυάζονται με άλλους τύπους ή σε conditional types (τύπους υπό συνθήκη).

Πρακτικά Παραδείγματα

1. Λογική υπό συνθήκη με διαμόρφωση:

Μπορείτε να χρησιμοποιήσετε boolean literal τύπους για να ελέγξετε τη συμπεριφορά μιας συνάρτησης βάσει ενός flag διαμόρφωσης:


interface FeatureFlags {
  darkMode: boolean;
  newUserFlow: boolean;
}

function initializeApp(flags: FeatureFlags) {
  if (flags.darkMode) {
    // Ενεργοποίηση dark mode
    console.log("Enabling dark mode...");
  } else {
    // Χρήση light mode
    console.log("Using light mode...");
  }

  if (flags.newUserFlow) {
    // Ενεργοποίηση ροής νέου χρήστη
    console.log("Enabling new user flow...");
  } else {
    // Χρήση παλιάς ροής χρήστη
    console.log("Using old user flow...");
  }
}

initializeApp({ darkMode: true, newUserFlow: false });

Ενώ αυτό το παράδειγμα χρησιμοποιεί τον τυπικό τύπο boolean, θα μπορούσατε να το συνδυάσετε με conditional types (που εξηγούνται παρακάτω) για να δημιουργήσετε πιο σύνθετη συμπεριφορά.

2. Discriminated Unions:

Οι boolean literal τύποι μπορούν να χρησιμοποιηθούν ως διακριτικοί (discriminators) σε union types. Εξετάστε το ακόλουθο παράδειγμα:


interface SuccessResult {
  success: true;
  data: any;
}

interface ErrorResult {
  success: false;
  error: string;
}

type Result = SuccessResult | ErrorResult;

function processResult(result: Result) {
  if (result.success) {
    console.log("Success:", result.data);
  } else {
    console.error("Error:", result.error);
  }
}

processResult({ success: true, data: { name: "John" } });
processResult({ success: false, error: "Failed to fetch data" });

Εδώ, η ιδιότητα success, η οποία είναι ένας boolean literal τύπος, λειτουργεί ως διακριτικός, επιτρέποντας στην TypeScript να περιορίσει τον τύπο του result εντός της εντολής if.

Συνδυασμός Literal Τύπων με Union Types

Οι literal τύποι είναι πιο ισχυροί όταν συνδυάζονται με union types (χρησιμοποιώντας τον τελεστή |). Αυτό σας επιτρέπει να ορίσετε έναν τύπο που μπορεί να περιέχει μία από πολλές συγκεκριμένες τιμές.

Πρακτικά Παραδείγματα

1. Ορισμός ενός τύπου κατάστασης:


type Status = "pending" | "in progress" | "completed" | "failed";

interface Task {
  id: number;
  description: string;
  status: Status;
}

const task1: Task = { id: 1, description: "Implement login", status: "in progress" }; // Έγκυρο
const task2: Task = { id: 2, description: "Implement logout", status: "done" };       // Σφάλμα: Type '{ id: number; description: string; status: string; }' is not assignable to type 'Task'.

Αυτό το παράδειγμα δείχνει πώς να επιβάλετε ένα συγκεκριμένο σύνολο επιτρεπόμενων τιμών κατάστασης για ένα αντικείμενο Task.

2. Ορισμός ενός τύπου συσκευής:

Σε μια εφαρμογή για κινητά, μπορεί να χρειαστεί να διαχειριστείτε διαφορετικούς τύπους συσκευών. Μπορείτε να χρησιμοποιήσετε μια ένωση (union) από string literal τύπους για να τους αναπαραστήσετε:


type DeviceType = "mobile" | "tablet" | "desktop";

function logDeviceType(device: DeviceType) {
  console.log(`Device type: ${device}`);
}

logDeviceType("mobile"); // Έγκυρο
logDeviceType("smartwatch"); // Σφάλμα: Argument of type '"smartwatch"' is not assignable to parameter of type 'DeviceType'.

Αυτό το παράδειγμα διασφαλίζει ότι η συνάρτηση logDeviceType καλείται μόνο με έγκυρους τύπους συσκευών.

Literal Τύποι με Type Aliases

Τα type aliases (ψευδώνυμα τύπων), χρησιμοποιώντας τη λέξη-κλειδί type, παρέχουν έναν τρόπο να δώσετε ένα όνομα σε έναν literal τύπο, κάνοντας τον κώδικά σας πιο ευανάγνωστο και συντηρήσιμο.

Πρακτικά Παραδείγματα

1. Ορισμός ενός τύπου κωδικού νομίσματος:


type CurrencyCode = "USD" | "EUR" | "GBP" | "JPY";

function formatCurrency(amount: number, currency: CurrencyCode): string {
  // ... υλοποίηση για τη μορφοποίηση του ποσού βάσει του κωδικού νομίσματος
  console.log(`Formatting ${amount} in ${currency}`);
  return "Formatted amount"; // Placeholder
}

formatCurrency(100, "USD"); // Έγκυρο
formatCurrency(200, "CAD"); // Σφάλμα: Argument of type '"CAD"' is not assignable to parameter of type 'CurrencyCode'.

Αυτό το παράδειγμα ορίζει ένα ψευδώνυμο τύπου CurrencyCode για ένα σύνολο κωδικών νομισμάτων, βελτιώνοντας την αναγνωσιμότητα της συνάρτησης formatCurrency.

2. Ορισμός ενός τύπου ημέρας της εβδομάδας:


type DayOfWeek = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday";

function isWeekend(day: DayOfWeek): boolean {
  return day === "Saturday" || day === "Sunday";
}

console.log(isWeekend("Monday"));   // false
console.log(isWeekend("Saturday")); // true
console.log(isWeekend("Funday"));   // Σφάλμα: Argument of type '"Funday"' is not assignable to parameter of type 'DayOfWeek'.

Συμπερασμός Τύπων Literal (Literal Inference)

Η TypeScript μπορεί συχνά να συμπεράνει αυτόματα τους literal τύπους βάσει των τιμών που αναθέτετε σε μεταβλητές. Αυτό είναι ιδιαίτερα χρήσιμο όταν εργάζεστε με μεταβλητές const.

Πρακτικά Παραδείγματα

1. Συμπερασμός String Literal Τύπων:


const apiKey = "your-api-key"; // Η TypeScript συμπεραίνει ότι ο τύπος του apiKey είναι "your-api-key"

function validateApiKey(key: "your-api-key") {
  return key === "your-api-key";
}

console.log(validateApiKey(apiKey)); // true

const anotherKey = "invalid-key";
console.log(validateApiKey(anotherKey)); // Σφάλμα: Argument of type 'string' is not assignable to parameter of type '"your-api-key"'.

Σε αυτό το παράδειγμα, η TypeScript συμπεραίνει ότι ο τύπος του apiKey είναι ο string literal τύπος "your-api-key". Ωστόσο, αν αναθέσετε μια μη σταθερή τιμή σε μια μεταβλητή, η TypeScript συνήθως θα συμπεράνει τον ευρύτερο τύπο string.

2. Συμπερασμός Number Literal Τύπων:


const port = 8080; // Η TypeScript συμπεραίνει ότι ο τύπος του port είναι 8080

function startServer(portNumber: 8080) {
  console.log(`Starting server on port ${portNumber}`);
}

startServer(port); // Έγκυρο

const anotherPort = 3000;
startServer(anotherPort); // Σφάλμα: Argument of type 'number' is not assignable to parameter of type '8080'.

Χρήση Literal Τύπων με Conditional Types

Οι literal τύποι γίνονται ακόμη πιο ισχυροί όταν συνδυάζονται με conditional types (τύπους υπό συνθήκη). Οι conditional types σας επιτρέπουν να ορίσετε τύπους που εξαρτώνται από άλλους τύπους, δημιουργώντας πολύ ευέλικτα και εκφραστικά συστήματα τύπων.

Βασική Σύνταξη

Η σύνταξη για έναν conditional type είναι:


TypeA extends TypeB ? TypeC : TypeD

Αυτό σημαίνει: αν ο TypeA μπορεί να ανατεθεί στον TypeB, τότε ο τελικός τύπος είναι ο TypeC. Διαφορετικά, ο τελικός τύπος είναι ο TypeD.

Πρακτικά Παραδείγματα

1. Αντιστοίχιση Κατάστασης με Μήνυμα:


type Status = "pending" | "in progress" | "completed" | "failed";

type StatusMessage = T extends "pending"
  ? "Waiting for action"
  : T extends "in progress"
  ? "Currently processing"
  : T extends "completed"
  ? "Task finished successfully"
  : "An error occurred";

function getStatusMessage(status: T): StatusMessage {
  switch (status) {
    case "pending":
      return "Waiting for action" as StatusMessage;
    case "in progress":
      return "Currently processing" as StatusMessage;
    case "completed":
      return "Task finished successfully" as StatusMessage;
    case "failed":
      return "An error occurred" as StatusMessage;
    default:
      throw new Error("Invalid status");
  }
}

console.log(getStatusMessage("pending"));    // Waiting for action
console.log(getStatusMessage("in progress")); // Currently processing
console.log(getStatusMessage("completed"));   // Task finished successfully
console.log(getStatusMessage("failed"));      // An error occurred

Αυτό το παράδειγμα ορίζει έναν τύπο StatusMessage που αντιστοιχίζει κάθε πιθανή κατάσταση σε ένα αντίστοιχο μήνυμα χρησιμοποιώντας conditional types. Η συνάρτηση getStatusMessage αξιοποιεί αυτόν τον τύπο για να παρέχει μηνύματα κατάστασης με ασφάλεια τύπων.

2. Δημιουργία ενός Type-Safe Event Handler:


type EventType = "click" | "mouseover" | "keydown";

type EventData = T extends "click"
  ? { x: number; y: number; } // Δεδομένα συμβάντος click
  : T extends "mouseover"
  ? { target: HTMLElement; }   // Δεδομένα συμβάντος mouseover
  : { key: string; }             // Δεδομένα συμβάντος keydown

function handleEvent(type: T, data: EventData) {
  console.log(`Handling event type ${type} with data:`, data);
}

handleEvent("click", { x: 10, y: 20 }); // Έγκυρο
handleEvent("mouseover", { target: document.getElementById("myElement")! }); // Έγκυρο
handleEvent("keydown", { key: "Enter" }); // Έγκυρο

handleEvent("click", { key: "Enter" }); // Σφάλμα: Argument of type '{ key: string; }' is not assignable to parameter of type '{ x: number; y: number; }'.

Αυτό το παράδειγμα δημιουργεί έναν τύπο EventData που ορίζει διαφορετικές δομές δεδομένων ανάλογα με τον τύπο του συμβάντος. Αυτό σας επιτρέπει να διασφαλίσετε ότι τα σωστά δεδομένα περνούν στη συνάρτηση handleEvent για κάθε τύπο συμβάντος.

Βέλτιστες Πρακτικές για τη Χρήση Literal Τύπων

Για να χρησιμοποιήσετε αποτελεσματικά τους literal τύπους στα έργα σας με TypeScript, λάβετε υπόψη τις ακόλουθες βέλτιστες πρακτικές:

Οφέλη από τη Χρήση Literal Τύπων

Συμπέρασμα

Οι literal τύποι της TypeScript είναι ένα ισχυρό χαρακτηριστικό που σας επιτρέπει να επιβάλετε αυστηρούς περιορισμούς τιμών, να βελτιώσετε την καθαρότητα του κώδικα και να αποτρέψετε σφάλματα. Κατανοώντας τη σύνταξη, τη χρήση και τα οφέλη τους, μπορείτε να αξιοποιήσετε τους literal τύπους για να δημιουργήσετε πιο στιβαρές και συντηρήσιμες εφαρμογές TypeScript. Από τον ορισμό παλετών χρωμάτων και API endpoints μέχρι τη διαχείριση διαφορετικών γλωσσών και τη δημιουργία type-safe event handlers, οι literal τύποι προσφέρουν ένα ευρύ φάσμα πρακτικών εφαρμογών που μπορούν να βελτιώσουν σημαντικά τη ροή εργασίας σας στην ανάπτυξη.