TypeScript - Interfaces
TypeScript - Interfaces
Section titled “TypeScript - Interfaces”An interface in TypeScript acts as a contract that defines the shape of an object. It specifies the names and types of properties and methods that an object must have to be considered compatible with that interface. Interfaces are a core feature of TypeScript’s structural type system (often called “duck typing”).
Interfaces only contain the declaration of members (properties, method signatures); they do not provide implementation details. It’s the responsibility of the object or class that uses the interface to adhere to its structure.
Interfaces are purely a compile-time construct. They are not compiled into JavaScript and have zero runtime overhead. Their purpose is solely for static type checking during development.
Declaring Interfaces
Section titled “Declaring Interfaces”Use the interface keyword followed by the interface name (typically starting with an uppercase ‘I’ by convention, though not strictly required, e.g., IPerson or Person).
Syntax
Section titled “Syntax”interface InterfaceName { propertyName: type; optionalProperty?: type; // Optional properties use ? readonly readOnlyProperty: type; // Readonly properties cannot be changed after initialization
methodName(param: type): returnType; optionalMethod?(param: type): returnType;}Example: Interface for an Object
Section titled “Example: Interface for an Object”interface Person { firstName: string; lastName: string; age?: number; // Optional property readonly id: number; sayHi: () => string; // Method signature}
// Object conforming to the Person interfaceconst customer: Person = { firstName: "Tom", lastName: "Hanks", id: 101, sayHi: (): string => { return "Hi there"; } // age is optional, so it's okay to omit};
// Another object conforming to the interfaceconst employee: Person = { firstName: "Jim", lastName: "Blakes", age: 35, id: 205, sayHi: () => "Hello!!!"};
console.log("Customer Object");console.log(customer.firstName); // Output: Tomconsole.log(customer.lastName); // Output: Hanksconsole.log(customer.sayHi()); // Output: Hi there// customer.id = 102; // Error: Cannot assign to 'id' because it is a read-only property.
console.log("\nEmployee Object");console.log(employee.firstName); // Output: Jimconsole.log(employee.age); // Output: 35TypeScript checks if an object has at least the properties defined by the interface with the correct types. Extra properties are generally allowed unless specifically checked (e.g., when assigning object literals directly).
Interfaces for Function Types
Section titled “Interfaces for Function Types”Interfaces can also describe the signature of a function.
interface SearchFunc { (source: string, subString: string): boolean;}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean { let result = src.search(sub); return result > -1;}
// Using arrow function syntaxconst myOtherSearch: SearchFunc = (source, substring) => { return source.includes(substring);};
console.log(mySearch("hello world", "world")); // Output: trueconsole.log(myOtherSearch("typescript", "java")); // Output: falseInterfaces for Array Types (Index Signatures)
Section titled “Interfaces for Array Types (Index Signatures)”Interfaces can define the type of index used to access array-like structures and the type of values stored.
// Describes an array-like object where keys are numbers and values are stringsinterface StringArray { [index: number]: string;}
let myStrArray: StringArray;myStrArray = ["Bob", "Alice"];console.log(myStrArray[0]); // Output: Bob// myStrArray[2] = 123; // Error: Type 'number' is not assignable to type 'string'.
// Describes an object where keys are strings and values are numbersinterface NumberDictionary { [index: string]: number; length: number; // Can have other specific properties too // name: string; // Error: Property 'name' of type 'string' is not assignable to string index type 'number'.}
let ageMap: NumberDictionary = { "Alice": 30, "Bob": 35, length: 2};
console.log(ageMap["Alice"]); // Output: 30console.log(ageMap.length); // Output: 2// ageMap["Charlie"] = "forty"; // Error: Type 'string' is not assignable to type 'number'.Note: If an interface has both a string and a number index signature, the type returned by the numeric indexer must be a subtype of the type returned by the string indexer. Also, properties with specific names must conform to the index signature type.
Extending Interfaces (Inheritance)
Section titled “Extending Interfaces (Inheritance)”Interfaces can inherit from one or more other interfaces using the extends keyword. This allows combining multiple interface definitions.
Syntax: Single Inheritance
Section titled “Syntax: Single Inheritance”interface ChildInterface extends ParentInterface { ... }Syntax: Multiple Inheritance
Section titled “Syntax: Multiple Inheritance”interface ChildInterface extends Parent1, Parent2, ... { ... }Example: Interface Inheritance
Section titled “Example: Interface Inheritance”interface Shape { color: string;}
interface PenStroke { penWidth: number;}
// Square extends Shape and PenStrokeinterface Square extends Shape, PenStroke { sideLength: number;}
let mySquare: Square = { color: "blue", penWidth: 5.0, sideLength: 10};
console.log(`Square Color: ${mySquare.color}`);console.log(`Square Pen Width: ${mySquare.penWidth}`);console.log(`Square Side Length: ${mySquare.sideLength}`);Interface vs. Type Alias
Section titled “Interface vs. Type Alias”TypeScript also has type aliases (type), which can often achieve similar results to interfaces, especially for defining object shapes.
type Point = { x: number; y: number;};
interface IPoint { x: number; y: number;}Key Differences:
- Extensibility: Interfaces can be declared multiple times and will be merged (Declaration Merging). Interfaces can also be extended using
extends. Type aliases cannot be merged after creation, although intersections (&) can combine types. - Implementation: Classes can
implementinterfaces to ensure they adhere to the contract. Classes cannot directlyimplementtype aliases that define object shapes (though they can implement types created from type aliases via intersections, etc.). - Flexibility: Type aliases are slightly more flexible; they can represent not just object shapes but also union types, intersection types, tuples, mapped types, and primitive type aliases (e.g.,
type UserID = string;).
Use interfaces primarily for defining object shapes or contracts that classes should implement. Use type aliases for more general type definitions, including unions, intersections, tuples, or when you need features not available with interfaces.