Lewati ke konten
Kembali ke Blog

Cara Belajar TypeScript untuk JavaScript Developer

· · 7 menit baca

TypeScript adalah superset JavaScript yang menambahkan static typing. Mari pelajari cara menggunakannya.

Apa itu TypeScript?

Keuntungan TypeScript

- Type safety (catch errors early)
- Better IDE support (autocomplete)
- Self-documenting code
- Refactoring lebih aman
- Better tooling
- Growing adoption

TypeScript vs JavaScript

// JavaScript
function add(a, b) {
  return a + b;
}
add("1", 2); // "12" (string concatenation)

// TypeScript function add(a: number, b: number): number { return a + b; } add("1", 2); // Error: Argument of type 'string' not assignable

Setup

Install TypeScript

# Global installation
npm install -g typescript

npm init -y npm install typescript --save-dev

Initialize tsconfig

npx tsc --init

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Compile dan Run

# Compile
npx tsc

Watch mode

npx tsc --watch

Run with ts-node

npm install -g ts-node ts-node src/index.ts

Basic Types

Primitive Types

// String
let nama: string = "Budi";

// Number let umur: number = 25; let harga: number = 99.99;

// Boolean let aktif: boolean = true;

// Any (avoid if possible) let data: any = "could be anything";

// Unknown (safer than any) let input: unknown = "something";

// Void (function returns nothing) function log(message: string): void { console.log(message); }

// Null dan Undefined let nullable: null = null; let undef: undefined = undefined;

Arrays

// Array of numbers
let numbers: number[] = [1, 2, 3];

// Alternative syntax let strings: Array<string> = ["a", "b", "c"];

// Mixed array with tuple let tuple: [string, number] = ["Budi", 25];

// Array of objects let users: { name: string; age: number }[] = [ { name: "Budi", age: 25 }, { name: "Ani", age: 23 }, ];

Objects

// Inline type
let user: { name: string; age: number } = {
  name: "Budi",
  age: 25,
};

// Optional property let config: { host: string; port?: number } = { host: "localhost", };

// Readonly let point: { readonly x: number; readonly y: number } = { x: 10, y: 20, }; // point.x = 5; // Error: Cannot assign to 'x'

Type Aliases dan Interfaces

Type Alias

// Basic type alias
type ID = string | number;

// Object type alias type User = { id: ID; name: string; email: string; age?: number; };

// Union type type Status = "pending" | "approved" | "rejected";

// Function type type MathFunc = (a: number, b: number) => number;

const add: MathFunc = (a, b) => a + b;

Interface

interface User {
  id: number;
  name: string;
  email: string;
}

// Extend interface interface Admin extends User { role: string; permissions: string[]; }

// Implement interface class AdminUser implements Admin { id: number; name: string; email: string; role: string; permissions: string[];

constructor(data: Admin) { this.id = data.id; this.name = data.name; this.email = data.email; this.role = data.role; this.permissions = data.permissions; } }

Type vs Interface

// Interface: can be extended/merged
interface User {
  name: string;
}
interface User {
  age: number;
}
// User now has both name and age

// Type: can use union/intersection type StringOrNumber = string | number; type Combined = User & { email: string };

// Recommendation: // - Use interface untuk objects // - Use type untuk unions, primitives, tuples

Functions

Function Types

// Parameter dan return types
function greet(name: string): string {
  return `Hello, ${name}!`;
}

// Arrow function const add = (a: number, b: number): number => a + b;

// Optional parameters function createUser(name: string, age?: number): User { return { name, age: age || 0 }; }

// Default parameters function greet(name: string = "World"): string { return Hello, ${name}!; }

// Rest parameters function sum(...numbers: number[]): number { return numbers.reduce((a, b) => a + b, 0); }

Function Overloading

function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return value.toFixed(2);
}

console.log(format("hello")); // "HELLO" console.log(format(3.14159)); // "3.14"

Generics

Basic Generics

// Generic function
function identity<T>(arg: T): T {
  return arg;
}

const str = identity<string>("hello"); const num = identity<number>(42); const inferred = identity("hello"); // Type inferred

// Generic array function firstElement<T>(arr: T[]): T | undefined { return arr[0]; }

Generic Interfaces

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

interface User { id: number; name: string; }

const userResponse: ApiResponse<User> = { data: { id: 1, name: "Budi" }, status: 200, message: "Success", };

Generic Constraints

interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(arg: T): number { console.log(arg.length); return arg.length; }

logLength("hello"); // 5 logLength([1, 2, 3]); // 3 // logLength(123); // Error: number doesn't have length

Utility Types

Built-in Utility Types

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// Partial - all properties optional type PartialUser = Partial<User>;

// Required - all properties required type RequiredUser = Required<PartialUser>;

// Pick - select properties type UserPreview = Pick<User, "id" | "name">;

// Omit - exclude properties type UserWithoutPassword = Omit<User, "password">;

// Readonly - all properties readonly type ReadonlyUser = Readonly<User>;

// Record - create object type type UserRoles = Record<string, string[]>; const roles: UserRoles = { admin: ["read", "write", "delete"], user: ["read"], };

Conditional Types

type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>; // "yes" type B = IsString<number>; // "no"

// Extract dan Exclude type T1 = Extract<"a" | "b" | "c", "a" | "f">; // "a" type T2 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"

Classes

Class with Types

class User {
  // Properties
  public id: number;
  public name: string;
  private password: string;
  protected email: string;
  readonly createdAt: Date;

constructor(id: number, name: string, password: string) { this.id = id; this.name = name; this.password = password; this.email = ""; this.createdAt = new Date(); }

// Method greet(): string { return Hello, ${this.name}!; }

// Getter get displayName(): string { return this.name.toUpperCase(); }

// Setter set displayName(value: string) { this.name = value; } }

Abstract Classes

abstract class Animal {
  constructor(public name: string) {}

abstract makeSound(): string;

move(): void { console.log( ${this.name} is moving); } }

class Dog extends Animal { makeSound(): string { return "Woof!"; } }

Type Guards

Type Narrowing

function printValue(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase()); // TypeScript knows it's string
  } else {
    console.log(value.toFixed(2)); // TypeScript knows it's number
  }
}

// instanceof class Dog { bark() { console.log("Woof!"); } } class Cat { meow() { console.log("Meow!"); } }

function makeSound(animal: Dog | Cat) { if (animal instanceof Dog) { animal.bark(); } else { animal.meow(); } }

// Custom type guard interface Fish { swim(): void; } interface Bird { fly(): void; }

function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; }

Modules

Export dan Import

// user.ts
export interface User {
  id: number;
  name: string;
}

export function createUser(name: string): User { return { id: Date.now(), name }; }

export default class UserService { // ... }

// main.ts import UserService, { User, createUser } from "./user";

const user: User = createUser("Budi");

Type-only Imports

import type { User } from "./user";
import { type User, createUser } from "./user";

Error Handling

Typed Errors

class AppError extends Error {
  constructor(message: string, public code: string, public statusCode: number) {
    super(message);
    this.name = "AppError";
  }
}

function fetchUser(id: number): User { if (id < 0) { throw new AppError("Invalid ID", "INVALID_ID", 400); } // ... }

try { fetchUser(-1); } catch (error) { if (error instanceof AppError) { console.log(error.code, error.statusCode); } }

Best Practices

Do’s

// Use strict mode
"strict": true

// Prefer interfaces for objects interface User { ... }

// Use const assertions const colors = ["red", "green", "blue"] as const;

// Use unknown instead of any function parse(input: unknown) { ... }

// Explicit return types for public APIs export function getUser(id: number): User { ... }

Don’ts

// Avoid any
let data: any; // ❌

// Avoid type assertions when possible const user = data as User; // ❌ if avoidable

// Avoid non-null assertions user!.name; // ❌ prefer optional chaining

// Don't ignore errors // @ts-ignore // ❌

Kesimpulan

TypeScript meningkatkan developer experience dengan type safety dan better tooling. Start dengan basics, gradually add more advanced types seiring kebutuhan.

Ditulis oleh

Hendra Wijaya

Tinggalkan Komentar

Email tidak akan ditampilkan.