TypeScript Mastery: Advanced Types dan Best Practices 2025
Artikel ini sepenuhnya ditulis oleh teknologi AI ( Claude Sonnet 4 ) dan direview Oleh Muhammad Aji Sukma
TypeScript telah menjadi standar industri untuk pengembangan JavaScript yang scalable dan maintainable. Dengan fitur-fitur advanced seperti conditional types, template literal types, dan powerful inference engine, TypeScript 5.x memberikan developer tools yang sangat powerful. Mari kita explore konsep-konsep advanced dan best practices untuk 2025.
๐ฏ Mengapa TypeScript Adalah Must-Have di 2025?
Keunggulan TypeScript Modern
1. Type Safety yang Comprehensive TypeScript mencegah runtime errors dengan mendeteksi masalah di compile time.
// Tanpa TypeScript - Prone to runtime errors
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Dengan TypeScript - Type-safe
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// Usage - Compile-time error jika type salah
const cartItems: CartItem[] = [
{ id: "1", name: "Laptop", price: 1000, quantity: 1 },
{ id: "2", name: "Mouse", price: 25, quantity: 2 },
];
const total = calculateTotal(cartItems); // โ
Type-safe
// calculateTotal("invalid"); // โ Compile error
2. Excellent IDE Support Autocomplete, refactoring, dan navigation yang superior.
interface User {
id: number;
name: string;
email: string;
profile?: {
avatar: string;
bio: string;
socialLinks: {
twitter?: string;
github?: string;
linkedin?: string;
};
};
}
// IDE akan memberikan autocomplete untuk semua properties
function displayUserInfo(user: User) {
console.log(`Name: ${user.name}`); // โ
Autocomplete
console.log(`Email: ${user.email}`); // โ
Autocomplete
// Optional chaining dengan type checking
if (user.profile?.avatar) {
// โ
Null-safe access
console.log(`Avatar: ${user.profile.avatar}`);
}
// IDE akan warning jika property tidak ada
// console.log(user.invalidProp); // โ Property doesn't exist
}
3. Better Refactoring & Maintenance TypeScript memudahkan refactoring large codebase dengan confidence.
๐๏ธ Advanced Type System
Utility Types untuk Transformation
// Base interface
interface BlogPost {
id: number;
title: string;
content: string;
author: string;
publishedAt: Date;
tags: string[];
featured: boolean;
status: "draft" | "published" | "archived";
}
// Partial - Make all properties optional
type BlogPostUpdate = Partial<BlogPost>;
// { id?: number; title?: string; content?: string; ... }
// Pick - Select specific properties
type BlogPostSummary = Pick<BlogPost, "id" | "title" | "author" | "publishedAt">;
// { id: number; title: string; author: string; publishedAt: Date }
// Omit - Exclude specific properties
type CreateBlogPost = Omit<BlogPost, "id" | "publishedAt">;
// Semua properties kecuali id dan publishedAt
// Required - Make all properties required
type CompleteBlogPost = Required<BlogPost>;
// Tidak ada property optional
// Record - Create object type with specific keys
type BlogPostsByTag = Record<string, BlogPost[]>;
// { [tag: string]: BlogPost[] }
// Usage examples
function updateBlogPost(id: number, updates: BlogPostUpdate): Promise<BlogPost> {
// Only specified properties can be updated
return api.patch(`/posts/${id}`, updates);
}
function getBlogPostSummaries(): Promise<BlogPostSummary[]> {
// Returns only summary data, not full content
return api.get("/posts/summaries");
}
function createBlogPost(data: CreateBlogPost): Promise<BlogPost> {
// id dan publishedAt akan di-generate server
return api.post("/posts", data);
}
// Type-safe grouping
function groupPostsByTag(posts: BlogPost[]): BlogPostsByTag {
return posts.reduce((acc, post) => {
post.tags.forEach((tag) => {
if (!acc[tag]) acc[tag] = [];
acc[tag].push(post);
});
return acc;
}, {} as BlogPostsByTag);
}
Generic Programming untuk Reusability
// Generic API response wrapper
interface ApiResponse<T> {
data: T;
success: boolean;
message: string;
meta?: {
total: number;
page: number;
limit: number;
};
}
// Generic API client
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async get<T>(endpoint: string): Promise<ApiResponse<T>> {
const response = await fetch(`${this.baseUrl}${endpoint}`);
return response.json();
}
async post<T, U = T>(endpoint: string, data: U): Promise<ApiResponse<T>> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
return response.json();
}
async put<T, U = T>(endpoint: string, data: U): Promise<ApiResponse<T>> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
return response.json();
}
}
// Usage dengan type safety
const api = new ApiClient("https://api.example.com");
// Type-safe API calls
const postsResponse = await api.get<BlogPost[]>("/posts");
// postsResponse.data is BlogPost[]
const newPost = await api.post<BlogPost, CreateBlogPost>("/posts", {
title: "New Post",
content: "Content here",
author: "John Doe",
tags: ["typescript", "programming"],
featured: false,
status: "draft",
});
// newPost.data is BlogPost
// Generic repository pattern
interface Repository<T, ID = number> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
create(data: Omit<T, "id">): Promise<T>;
update(id: ID, data: Partial<T>): Promise<T>;
delete(id: ID): Promise<boolean>;
}
class BlogPostRepository implements Repository<BlogPost> {
private api: ApiClient;
constructor(api: ApiClient) {
this.api = api;
}
async findById(id: number): Promise<BlogPost | null> {
try {
const response = await this.api.get<BlogPost>(`/posts/${id}`);
return response.data;
} catch {
return null;
}
}
async findAll(): Promise<BlogPost[]> {
const response = await this.api.get<BlogPost[]>("/posts");
return response.data;
}
async create(data: Omit<BlogPost, "id">): Promise<BlogPost> {
const response = await this.api.post<BlogPost>("/posts", data);
return response.data;
}
async update(id: number, data: Partial<BlogPost>): Promise<BlogPost> {
const response = await this.api.put<BlogPost>(`/posts/${id}`, data);
return response.data;
}
async delete(id: number): Promise<boolean> {
try {
await this.api.delete(`/posts/${id}`);
return true;
} catch {
return false;
}
}
}
Conditional Types untuk Advanced Logic
// Conditional types berdasarkan properties
type NonNullable<T> = T extends null | undefined ? never : T;
// Extract function return type
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
// Advanced conditional types
type ApiEndpoint<T> = T extends "user"
? "/api/users"
: T extends "post"
? "/api/posts"
: T extends "comment"
? "/api/comments"
: never;
// Usage
type UserEndpoint = ApiEndpoint<"user">; // '/api/users'
type PostEndpoint = ApiEndpoint<"post">; // '/api/posts'
// Conditional types untuk form validation
type ValidationRule<T> = {
required?: boolean;
minLength?: T extends string ? number : never;
maxLength?: T extends string ? number : never;
min?: T extends number ? number : never;
max?: T extends number ? number : never;
pattern?: T extends string ? RegExp : never;
custom?: (value: T) => boolean | string;
};
interface FormField<T> {
name: string;
value: T;
rules?: ValidationRule<T>;
}
// Type-safe form validation
const userForm = {
name: {
name: "name",
value: "",
rules: {
required: true,
minLength: 2, // โ
Valid for string
maxLength: 50, // โ
Valid for string
// min: 10, // โ Invalid for string
},
} as FormField<string>,
age: {
name: "age",
value: 0,
rules: {
required: true,
min: 18, // โ
Valid for number
max: 100, // โ
Valid for number
// minLength: 2, // โ Invalid for number
},
} as FormField<number>,
};
// Template literal types untuk dynamic string types
type EventName = "click" | "hover" | "focus";
type ElementType = "button" | "input" | "div";
type EventHandler<
E extends EventName,
T extends ElementType
> = `on${Capitalize<E>}${Capitalize<T>}`;
// Generated types: 'onClickButton', 'onHoverInput', etc.
type ButtonClickHandler = EventHandler<"click", "button">; // 'onClickButton'
// Advanced mapped types
type EventHandlers = {
[K in EventName as `on${Capitalize<K>}`]: (event: Event) => void;
};
// { onClick: (event: Event) => void; onHover: (event: Event) => void; onFocus: (event: Event) => void; }
๐ ๏ธ Advanced Patterns & Techniques
Decorator Pattern dengan Types
// Method decorator untuk logging
function Log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyName} with args:`, args);
const result = method.apply(this, args);
console.log(`Result:`, result);
return result;
};
}
// Class decorator untuk validation
function Validate<T extends new (...args: any[]) => {}>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(...args);
this.validate();
}
validate() {
// Validation logic
console.log("Validating instance");
}
};
}
// Usage
@Validate
class User {
constructor(public name: string, public email: string) {}
@Log
getName(): string {
return this.name;
}
@Log
setEmail(email: string): void {
this.email = email;
}
}
const user = new User("John", "john@example.com");
user.getName(); // Logs method call and result
Advanced Error Handling dengan Types
// Result type untuk error handling
type Result<T, E = Error> = { success: true; data: T } | { success: false; error: E };
// Type-safe error handling
async function fetchUserData(id: number): Promise<Result<User, string>> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
return { success: false, error: `HTTP ${response.status}` };
}
const userData = await response.json();
return { success: true, data: userData };
} catch (error) {
return { success: false, error: "Network error" };
}
}
// Usage dengan type narrowing
async function handleUserData(id: number) {
const result = await fetchUserData(id);
if (result.success) {
// TypeScript knows result.data is User
console.log(result.data.name);
} else {
// TypeScript knows result.error is string
console.error(result.error);
}
}
// Custom error types
abstract class AppError extends Error {
abstract readonly statusCode: number;
abstract readonly isOperational: boolean;
}
class ValidationError extends AppError {
readonly statusCode = 400;
readonly isOperational = true;
constructor(message: string, public readonly field: string) {
super(message);
this.name = "ValidationError";
}
}
class NotFoundError extends AppError {
readonly statusCode = 404;
readonly isOperational = true;
constructor(resource: string, id: string | number) {
super(`${resource} with id ${id} not found`);
this.name = "NotFoundError";
}
}
// Type-safe error handling function
function handleError(error: AppError): { statusCode: number; message: string } {
if (error instanceof ValidationError) {
return {
statusCode: error.statusCode,
message: `Validation failed for field: ${error.field}. ${error.message}`,
};
}
if (error instanceof NotFoundError) {
return {
statusCode: error.statusCode,
message: error.message,
};
}
return {
statusCode: 500,
message: "Internal server error",
};
}
Type Guards dan Narrowing
// Built-in type guards
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isNumber(value: unknown): value is number {
return typeof value === "number" && !isNaN(value);
}
function isArray<T>(value: unknown): value is T[] {
return Array.isArray(value);
}
// Custom type guards
interface Cat {
type: "cat";
meow(): void;
}
interface Dog {
type: "dog";
bark(): void;
}
type Pet = Cat | Dog;
function isCat(pet: Pet): pet is Cat {
return pet.type === "cat";
}
function isDog(pet: Pet): pet is Dog {
return pet.type === "dog";
}
// Usage dengan type narrowing
function handlePet(pet: Pet) {
if (isCat(pet)) {
pet.meow(); // โ
TypeScript knows pet is Cat
} else if (isDog(pet)) {
pet.bark(); // โ
TypeScript knows pet is Dog
}
}
// Advanced type guards dengan generic
function hasProperty<T, K extends PropertyKey>(obj: T, key: K): obj is T & Record<K, unknown> {
return obj != null && typeof obj === "object" && key in obj;
}
// Usage
function processData(data: unknown) {
if (hasProperty(data, "name") && isString(data.name)) {
console.log(`Name: ${data.name}`); // โ
Type-safe
}
if (hasProperty(data, "age") && isNumber(data.age)) {
console.log(`Age: ${data.age}`); // โ
Type-safe
}
}
๐ง Development Tools & Configuration
Advanced tsconfig.json
{
"compilerOptions": {
// Target & Module
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"moduleResolution": "bundler",
// Strict Type-Checking
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noImplicitReturns": true,
"noImplicitOverride": true,
"noUncheckedIndexedAccess": true,
// Module Resolution
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/utils/*": ["src/utils/*"],
"@/types/*": ["src/types/*"]
},
// Emit
"outDir": "./dist",
"removeComments": true,
"noEmitOnError": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
// Interop Constraints
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
// Advanced
"skipLibCheck": true,
"exactOptionalPropertyTypes": true,
"noImplicitThis": true,
"alwaysStrict": true
},
"include": ["src/**/*", "tests/**/*"],
"exclude": ["node_modules", "dist", "**/*.js"]
}
ESLint Integration
// .eslintrc.json
{
"extends": [
"eslint:recommended",
"@typescript-eslint/recommended",
"@typescript-eslint/recommended-requiring-type-checking"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/prefer-nullish-coalescing": "error",
"@typescript-eslint/prefer-optional-chain": "error",
"@typescript-eslint/no-floating-promises": "error"
}
}
๐งช Testing dengan TypeScript
Jest dengan TypeScript
// jest.config.js
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/src", "<rootDir>/tests"],
testMatch: ["**/__tests__/**/*.+(ts|tsx|js)", "**/*.(test|spec).+(ts|tsx|js)"],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
collectCoverageFrom: ["src/**/*.{ts,tsx}", "!src/**/*.d.ts"],
setupFilesAfterEnv: ["<rootDir>/tests/setup.ts"],
};
// Type-safe testing
// tests/BlogPost.test.ts
import { BlogPost, CreateBlogPost } from "../src/types/BlogPost";
import { BlogPostRepository } from "../src/repositories/BlogPostRepository";
describe("BlogPostRepository", () => {
let repository: BlogPostRepository;
let mockApiClient: jest.Mocked<ApiClient>;
beforeEach(() => {
mockApiClient = {
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
delete: jest.fn(),
} as jest.Mocked<ApiClient>;
repository = new BlogPostRepository(mockApiClient);
});
describe("findById", () => {
it("should return blog post when found", async () => {
const mockPost: BlogPost = {
id: 1,
title: "Test Post",
content: "Test Content",
author: "John Doe",
publishedAt: new Date("2025-09-09"),
tags: ["test"],
featured: false,
status: "published",
};
mockApiClient.get.mockResolvedValue({
data: mockPost,
success: true,
message: "Success",
});
const result = await repository.findById(1);
expect(result).toEqual(mockPost);
expect(mockApiClient.get).toHaveBeenCalledWith("/posts/1");
});
it("should return null when post not found", async () => {
mockApiClient.get.mockRejectedValue(new Error("Not found"));
const result = await repository.findById(999);
expect(result).toBeNull();
});
});
describe("create", () => {
it("should create new blog post", async () => {
const createData: CreateBlogPost = {
title: "New Post",
content: "New Content",
author: "Jane Doe",
tags: ["new"],
featured: true,
status: "draft",
};
const createdPost: BlogPost = {
id: 2,
publishedAt: new Date(),
...createData,
};
mockApiClient.post.mockResolvedValue({
data: createdPost,
success: true,
message: "Created",
});
const result = await repository.create(createData);
expect(result).toEqual(createdPost);
expect(mockApiClient.post).toHaveBeenCalledWith("/posts", createData);
});
});
});
๐ Best Practices untuk Production
Project Structure
src/
โโโ types/ # Type definitions
โ โโโ api.ts # API types
โ โโโ entities.ts # Domain entities
โ โโโ utils.ts # Utility types
โโโ services/ # Business logic
โ โโโ BlogService.ts
โ โโโ UserService.ts
โโโ repositories/ # Data access layer
โ โโโ BlogRepository.ts
โ โโโ UserRepository.ts
โโโ utils/ # Utility functions
โ โโโ validation.ts
โ โโโ formatting.ts
โโโ components/ # UI components (if using React)
โ โโโ BlogCard.tsx
โ โโโ UserProfile.tsx
โโโ hooks/ # Custom hooks (if using React)
โ โโโ useBlogPosts.ts
โ โโโ useAuth.ts
โโโ __tests__/ # Test files
โโโ services/
โโโ repositories/
Code Quality Rules
// 1. Always use explicit return types untuk public APIs
export function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// 2. Use const assertions untuk immutable data
const BLOG_CATEGORIES = ["tech", "design", "business"] as const;
type BlogCategory = (typeof BLOG_CATEGORIES)[number]; // 'tech' | 'design' | 'business'
// 3. Prefer union types over enums
type PostStatus = "draft" | "published" | "archived";
// 4. Use branded types untuk domain-specific values
type UserId = number & { readonly brand: unique symbol };
type PostId = number & { readonly brand: unique symbol };
function createUserId(id: number): UserId {
return id as UserId;
}
// 5. Always handle nullable values explicitly
function getUserName(user: User | null): string {
return user?.name ?? "Anonymous";
}
// 6. Use readonly untuk immutable data
interface ImmutablePost {
readonly id: number;
readonly title: string;
readonly content: string;
readonly tags: readonly string[];
}
// 7. Prefer composition over inheritance
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Authored {
authorId: number;
author: string;
}
type BlogPost = Timestamped &
Authored & {
id: number;
title: string;
content: string;
};
๐ Kesimpulan
TypeScript di 2025 adalah essential skill untuk modern JavaScript development. Dengan menguasai:
Advanced Type System:
- โ Utility Types untuk type transformations
- โ Generic Programming untuk reusable code
- โ Conditional Types untuk advanced logic
- โ Template Literal Types untuk string manipulation
Development Best Practices:
- โ Strict Configuration untuk maximum type safety
- โ Custom Type Guards untuk runtime type checking
- โ Branded Types untuk domain-specific values
- โ Result Types untuk error handling
Production Ready:
- โ Comprehensive Testing dengan type safety
- โ ESLint Integration untuk code quality
- โ Proper Project Structure yang scalable
- โ Performance Optimization dengan tree shaking
Advanced Patterns:
- โ Decorator Pattern untuk cross-cutting concerns
- โ Repository Pattern dengan generics
- โ Builder Pattern dengan fluent APIs
- โ Facade Pattern untuk API simplification
TypeScript memberikan confidence dan productivity yang sangat tinggi dalam pengembangan aplikasi kompleks. Investasi waktu untuk belajar TypeScript akan memberikan ROI yang sangat besar dalam jangka panjang.
Pro Tips:
- Enable strict mode dan semua strict flags
- Use type guards untuk runtime type checking
- Leverage utility types daripada membuat custom types
- Write comprehensive tests dengan type safety
- Always prefer composition over inheritance
Happy coding with TypeScript! ๐