Mengenal TypeScript: Interface & Type

This article has been translated into English version: Learn TypeScript: Interface & Type
Sebelumnya kita sudah pernah membahas tentang perbedaan Type dan Interface di Mengenal TypeScript: Tipe Data Primitif, Type Alias, dan Interface, tapi kali ini kita akan membahas lebih detil tentang tiga aspek penting pada Type dan Interface: Function Interface, Extending Interface, dan Property Bertipe Data Function di Interface/Type.
Function Interface
Interface di TypeScript tidak hanya bisa digunakan untuk mendefinisikan bentuk objek, tetapi juga untuk mendefinisikan bentuk fungsi. Ini disebut dengan function interface.
Mendefinisikan Function Interface
Berikut ini cara mendefinisikan function interface di TypeScript.
/** Mendefinisikan function interface */
interface MathOperation {
(x: number, y: number): number;
}
/** Mengimplementasikan function interface */
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
Pada contoh di atas, MathOperation
adalah function interface yang mendefinisikan fungsi dengan dua parameter bertipe number
dan mengembalikan nilai bertipe number
.
Function Interface dengan Property
Function interface juga bisa memiliki property tambahan:
interface FetchFunction {
(url: string, options?: object): Promise<Response>;
timeout: number;
cache?: boolean;
}
const fetchData: FetchFunction = async (url, options) => {
// Implementasi fetch
return new Response();
};
// Menambahkan property ke fungsi
fetchData.timeout = 3000;
fetchData.cache = true;
console.log(fetchData.timeout); // 3000
Function Interface vs Type Alias
Function interface juga bisa dibuat menggunakan type alias:
/** Menggunakan interface */
interface Logger {
(message: string): void;
}
/** Menggunakan type alias */
type LoggerType = (message: string) => void;
/** Keduanya bisa digunakan dengan cara yang sama */
const consoleLogger: Logger = (msg) => console.log(msg);
const fileLogger: LoggerType = (msg) => {
// Implementasi logging ke file
};
Extending Interface
Salah satu kekuatan interface di TypeScript adalah kemampuannya untuk diperluas (extended) atau diwarisi. Ini memungkinkan kita untuk membuat interface baru berdasarkan interface yang sudah ada.
Basic Interface Extension
Ini adalah contoh paling sederhana ketika ingin melakukan extend di Interface.
/** Interface dasar */
interface Person {
name: string;
age: number;
}
/** Interface yang memperluas Person */
interface Employee extends Person {
employeeId: string;
department: string;
}
/** Objek harus memiliki semua properti dari Person dan Employee */
const employee: Employee = {
name: "Budi",
age: 30,
employeeId: "E001",
department: "Engineering"
};
Multiple Interface Extension
Interface juga bisa memperluas lebih dari satu interface:
interface HasName {
name: string;
}
interface HasAge {
age: number;
}
interface HasAddress {
address: string;
}
/** Memperluas multiple interface */
interface Contact extends HasName, HasAge, HasAddress {
email: string;
phone: string;
}
const contact: Contact = {
name: "Ani",
age: 25,
address: "Jl. Merdeka No. 123",
email: "ani@example.com",
phone: "08123456789"
};
Extending Interface dengan Generics
Interface juga bisa diperluas dengan menggunakan generics:
interface BaseResponse<T> {
status: number;
message: string;
timestamp: Date;
}
interface DataResponse<T> extends BaseResponse<T> {
data: T;
}
/** Menggunakan interface dengan generics */
const userResponse: DataResponse<{ id: number; username: string }> = {
status: 200,
message: "Success",
timestamp: new Date(),
data: {
id: 1,
username: "johndoe"
}
};
Penjelasan lebih mendalam tentang konsep Generics pada TypeScript bisa dibaca di sini: Mengenal TypeScript: Generics
Property Bertipe Data Function di Interface/Type
Interface dan type alias di TypeScript bisa memiliki property yang bertipe data function. Ini sangat berguna untuk mendefinisikan objek dengan method atau callback.
Method di Interface
/** Interface dengan method */
interface Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
multiply?(a: number, b: number): number; // Optional method
}
/** Implementasi interface */
const simpleCalc: Calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
};
const advancedCalc: Calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
};
console.log(simpleCalc.add(5, 3)); // 8
console.log(advancedCalc.multiply?.(5, 3)); // 15
Function Property dengan Signature Eksplisit
Interface dapat mendefinisikan property bertipe function dengan signature yang eksplisit, menentukan dengan jelas parameter dan return type. Pendekatan ini meningkatkan type safety dan membuat kontrak interface lebih jelas bagi developer yang mengimplementasikannya.
Perbedaannya dengan method di interface adalah pada sintaks dan penggunaannya:
Sintaks
Method menggunakan sintaks methodName(params): returnType
, sedangkan function property menggunakan sintaks propertyName: (params) => returnType
.
Semantik
Method menekankan bahwa fungsi adalah bagian dari perilaku objek, sedangkan function property memperlakukan fungsi sebagai data yang disimpan dalam properti.
Penggunaan dalam konteks inheritance
Method lebih mudah di-override dalam konteks inheritance, sementara function property menekankan pendekatan yang lebih fungsional.
interface EventHandler {
/** Property bertipe function dengan signature eksplisit */
onClick: (event: MouseEvent) => void;
onHover: (event: MouseEvent) => boolean;
onSubmit(formData: object): Promise<Response>;
}
const handler: EventHandler = {
onClick: (event) => {
console.log("Clicked", event.target);
},
onHover: (event) => {
console.log("Hover", event.target);
return true;
},
onSubmit: async (formData) => {
// Implementasi submit
return new Response();
},
};
Function Property dengan Generics
Kita juga bisa menggunakan generics ketika mendeklarasikan sebuah function property pada interface:
interface DataProcessor<T, R> {
process: (data: T) => R;
validate: (data: T) => boolean;
}
// Implementasi untuk string dan number
const stringToNumber: DataProcessor<string, number> = {
process: (data) => parseInt(data, 10),
validate: (data) => !isNaN(Number(data)),
};
console.log(stringToNumber.process("42")); // 42
console.log(stringToNumber.validate("42")); // true
console.log(stringToNumber.validate("abc")); // false
Penjelasan lebih mendalam tentang konsep Generics pada TypeScript bisa dibaca di sini: Mengenal TypeScript: Generics
Callback Pattern
Function property sangat berguna untuk mendefinisikan callback pattern:
interface FetchOptions {
url: string;
method: "GET" | "POST" | "PUT" | "DELETE";
headers?: Record<string, string>;
body?: object;
onSuccess: (data: any) => void;
onError: (error: Error) => void;
onComplete?: () => void;
}
function fetchWithCallbacks(options: FetchOptions): void {
try {
// Simulasi fetch
const data = { result: "success" };
options.onSuccess(data);
} catch (error) {
options.onError(error as Error);
} finally {
options.onComplete?.();
}
}
// Menggunakan function dengan callback
fetchWithCallbacks({
url: "https://api.example.com/data",
method: "GET",
onSuccess: (data) => console.log("Data:", data),
onError: (error) => console.error("Error:", error),
onComplete: () => console.log("Request completed"),
});
Contoh kode di atas mengilustrasikan implementasi callback pattern dalam TypeScript menggunakan interface. Interface FetchOptions
mendefinisikan struktur untuk melakukan HTTP request dengan callback functions.
Pada contoh tersebut:
onSuccess
adalah callback wajib yang dipanggil ketika request berhasilonError
adalah callback wajib untuk menangani kesalahanonComplete
adalah callback opsional yang dipanggil setelah request selesai (berhasil atau gagal)
Function fetchWithCallbacks
menerima objek yang sesuai dengan interface FetchOptions
dan menjalankan callback yang sesuai berdasarkan hasil operasi. Pola ini sangat berguna untuk operasi asynchronous karena memungkinkan kode pemanggil menentukan apa yang harus dilakukan dengan hasil operasi tanpa harus menunggu operasi selesai.
Penggunaan optional chaining (?.
) pada options.onComplete?.()
menunjukkan keunggulan TypeScript dalam menangani callback opsional dengan aman.
Function Property vs Method Syntax
TypeScript menyediakan dua cara untuk mendefinisikan method di interface:
interface User {
/** Method syntax */
getFullName(): string;
/** Function property syntax */
calculateAge: (birthYear: number) => number;
}
const user: User = {
getFullName() {
return "John Doe";
},
calculateAge: (birthYear) => new Date().getFullYear() - birthYear,
};
console.log(user.getFullName()); // "John Doe"
console.log(user.calculateAge(1990)); // 35 (di tahun 2025)
Kedua pendekatan ini secara fungsional identik, tetapi method syntax lebih ringkas dan lebih mirip dengan sintaks class di JavaScript.
Kesimpulan
Interface dan Type di TypeScript menawarkan cara yang fleksibel dan kuat untuk mendefinisikan bentuk data dalam aplikasi kita. Function interface memungkinkan kita mendefinisikan kontrak untuk fungsi, extending interface memungkinkan kita membuat hierarki tipe yang terstruktur, dan property bertipe function memberikan kita kemampuan untuk mendefinisikan objek dengan perilaku.
Dengan memahami ketiga konsep ini, kita dapat membuat kode TypeScript yang lebih ekspresif, reusable, dan type-safe. Pendekatan functional programming dengan TypeScript menjadi lebih mudah ketika kita bisa mendefinisikan kontrak fungsi dengan jelas dan memanfaatkan property bertipe function untuk membuat kode yang lebih modular.