Zum Inhalt springen

🛡️ One-Stop Guide to Type Safety in TypeScript: All Ways to Perform Type Checks

TypeScript is a powerful superset of JavaScript that introduces static typing. It allows us to write safer, more predictable code. But just defining types isn’t enough — in real-world applications, we often deal with runtime data (user input, API responses, dynamic objects, etc.) that can bypass the compiler’s safety net.

This blog post serves as your one-stop solution for all type checking techniques in TypeScript, covering basic to advanced concepts with organized sections and practical code examples.

🧱 1. TypeScript Basics: Compile-Time Type Checking

✅ Static Type Annotations

let age: number = 30;
let name: string = "Alice";
let isActive: boolean = true;

✅ Arrays and Tuples

let scores: number[] = [90, 85, 70];
let user: [string, number] = ["Bob", 25]; // Tuple

✅ Enums

enum Role {
  Admin,
  User,
  Guest,
}

const userRole: Role = Role.User;

🧾 2. Using typeof for Primitive Runtime Checks

TypeScript types vanish at runtime, so we use JS operators like typeof to check types dynamically.

function logValue(value: unknown) {
  if (typeof value === "string") {
    console.log("It's a string:", value.toUpperCase());
  } else if (typeof value === "number") {
    console.log("It's a number:", value.toFixed(2));
  } else {
    console.log("Unknown type");
  }
}

Supported typeof types:

  • "string"
  • "number"
  • "boolean"
  • "undefined"
  • "object"
  • "function"
  • "symbol"
  • "bigint"

📏 3. Using instanceof for Class Checks

Use instanceof to check if an object is an instance of a class.

class Animal {
  name: string = "Animal";
}

class Dog extends Animal {
  breed: string = "Labrador";
}

const pet = new Dog();

if (pet instanceof Dog) {
  console.log(pet.breed); // Labrador
}

🪪 4. Type Guards (Custom Runtime Type Checks)

✅ Type Predicate Function

type User = { name: string; age: number };
type Admin = { name: string; role: string };

function isAdmin(user: User | Admin): user is Admin {
  return (user as Admin).role !== undefined;
}

const u: User | Admin = { name: "Alex", role: "admin" };

if (isAdmin(u)) {
  console.log("Admin role is:", u.role);
}

🔥 user is Admin is a type predicate that narrows the type inside conditionals.

🧱 5. Discriminated Unions

Very powerful way to model and type-check multiple variants.

type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; size: number };
type Shape = Circle | Square;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.size ** 2;
  }
}

✅ Discriminated unions help safely branch logic based on a common kind property.

📚 6. in Operator for Property Checks

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    animal.swim();
  } else {
    animal.fly();
  }
}

✅ Use the in operator when there’s no common discriminator (kind field).

🧮 7. Type Assertions (Be Careful!)

const someValue: unknown = "Hello World";

const strLength: number = (someValue as string).length;

⚠️ Type assertions override the compiler. Use sparingly when you’re absolutely sure.

🧊 8. Using typeof with Functions and Objects

const config = {
  port: 3000,
  host: "localhost",
};

type Config = typeof config; // Inferred type of object

function connect(cfg: Config) {
  console.log(`Connecting to ${cfg.host}:${cfg.port}`);
}

📏 9. Using keyof, Record, Partial, Pick, Omit

These are utility types to check and manipulate types.

type User = {
  id: number;
  name: string;
  email: string;
};

type PartialUser = Partial<User>;
type PickName = Pick<User, "name">;
type WithoutEmail = Omit<User, "email">;
type UserRecord = Record<string, User>;
type UserKeys = keyof User; // "id" | "name" | "email"

🔍 10. Validating JSON or API Responses

✅ Manual Runtime Check Example

type User = { id: number; name: string };

function isUser(data: any): data is User {
  return (
    typeof data === "object" &&
    typeof data.id === "number" &&
    typeof data.name === "string"
  );
}

✅ With zod or io-ts (Schema Validation)

import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
});

type User = z.infer<typeof UserSchema>;

const result = UserSchema.safeParse(JSON.parse(json));

if (result.success) {
  console.log("Valid user:", result.data);
}

🔥 Use libraries like zod, io-ts, or yup for runtime validation in production.

🧪 11. Advanced: Exhaustive Checking

Make sure you handle all variants in discriminated unions.

function handle(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return shape.radius;
    case "square":
      return shape.size;
    default:
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

✅ This triggers a compile-time error if you miss a case.

🛠️ 12. Third-party Runtime Validators (Quick Overview)

Library Usage Notes
zod Runtime validation + TypeScript Modern and typesafe
io-ts Powerful validation + FP Functional style
yup Schema-based validation JS-friendly
class-validator Decorator-based validation Works with classes

🧪 13. unknown vs any

Type Description
any Opt-out of type checking (dangerous)
unknown Safer, forces type narrowing or checks
function process(input: unknown) {
  if (typeof input === "string") {
    console.log(input.toUpperCase());
  }
}

🪖 14. Type Narrowing in Complex Conditions

function example(value: string | string[] | null) {
  if (value && typeof value === "string") {
    console.log("It's a string:", value);
  } else if (Array.isArray(value)) {
    console.log("Array:", value.join(", "));
  }
}

💥 Bonus: Type-Safe API Layer

type ApiResponse<T> = {
  status: number;
  data: T;
  error?: string;
};

async function fetchUser(): Promise<ApiResponse<User>> {
  const res = await fetch("/api/user");
  const json = await res.json();

  if (!isUser(json)) {
    return { status: 500, data: null as any, error: "Invalid data" };
  }

  return { status: 200, data: json };
}

📦 Tools & Plugins for Type Safety

  • ESLint + TypeScript rules
  • tsconfig strict mode
  • ts-node for dev runtime
  • typescript-json-schema for schema generation

✅ Final Checklist for Type Safety

Task Technique
Compile-time safety Type annotations, interfaces, types
Runtime safety Type guards, typeof, instanceof
Object validation Schema validators (zod, io-ts, etc.)
Exhaustive checks Discriminated unions + never
API input/output typing Generic wrappers, type-safe contracts

🧠 Conclusion

TypeScript gives you the tools, but it’s up to you to use them wisely.
Think of type checking as both a compile-time shield and a runtime armor. 🛡️

With all these strategies and techniques, you’re now ready to build robust, type-safe applications that prevent bugs and improve maintainability.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert