Prerequisites
- Understanding of basic interfaces ๐
- Knowledge of TypeScript types ๐
- Familiarity with object-oriented concepts ๐ป
What you'll learn
- Master interface extension syntax ๐ฏ
- Build complex type hierarchies ๐๏ธ
- Understand multiple interface extension ๐
- Apply interface composition patterns โจ
๐ฏ Introduction
Welcome to the architectural world of interface extension! ๐ In this guide, weโll explore how to build complex type structures by extending and composing interfaces, creating powerful and flexible type hierarchies.
Youโll discover how interface extension is like building with LEGO blocks ๐งฑ - you start with basic pieces and combine them to create magnificent structures. Whether youโre designing API schemas ๐, modeling domain entities ๐ข, or creating component prop hierarchies ๐จ, mastering interface extension is crucial for scalable TypeScript applications.
By the end of this tutorial, youโll be confidently architecting complex type systems that are both powerful and maintainable! Letโs build! ๐โโ๏ธ
๐ Understanding Interface Extension
๐ค What is Interface Extension?
Interface extension allows you to build new interfaces based on existing ones, inheriting all their properties and methods. Itโs like evolution in nature ๐ฆ - each generation builds upon the previous one, adding new capabilities while keeping the best features.
In TypeScript, interface extension:
- โจ Creates type hierarchies through inheritance
- ๐ Promotes code reuse and DRY principles
- ๐ก๏ธ Maintains type safety across inheritance chains
- ๐ง Enables flexible and composable type design
๐ก Why Extend Interfaces?
Hereโs why developers love interface extension:
- Code Reusability โป๏ธ: Donโt repeat yourself
- Type Hierarchies ๐๏ธ: Model real-world relationships
- Incremental Design ๐: Build complexity gradually
- Maintainability ๐ง: Change base types, update everywhere
Real-world example: Think of a vehicle hierarchy ๐. You start with a basic Vehicle
interface, extend it to create Car
, Truck
, and Motorcycle
, each adding specific properties while inheriting common ones like speed
and color
.
๐ง Basic Interface Extension
๐ Simple Extension Example
Letโs start with a friendly example:
// ๐ฎ Base character interface
interface Character {
id: string;
name: string;
health: number;
position: { x: number; y: number };
}
// ๐ฆธ Hero extends Character
interface Hero extends Character {
level: number;
experience: number;
skills: string[];
mana: number;
}
// ๐น Enemy extends Character
interface Enemy extends Character {
damage: number;
attackSpeed: number;
loot: string[];
difficulty: 'easy' | 'medium' | 'hard';
}
// โ
Create a hero
const warrior: Hero = {
id: 'hero_001',
name: 'Aragorn',
health: 100,
position: { x: 10, y: 20 },
level: 15,
experience: 3500,
skills: ['Sword Strike', 'Shield Bash', 'Battle Cry'],
mana: 50
};
// โ
Create an enemy
const goblin: Enemy = {
id: 'enemy_042',
name: 'Sneaky Goblin',
health: 30,
position: { x: 50, y: 60 },
damage: 10,
attackSpeed: 1.5,
loot: ['Gold Coin', 'Rusty Dagger'],
difficulty: 'easy'
};
// ๐ฏ Function that works with base type
function moveCharacter(character: Character, dx: number, dy: number): void {
character.position.x += dx;
character.position.y += dy;
console.log(`${character.name} moved to (${character.position.x}, ${character.position.y}) ๐`);
}
// โจ Works with both Hero and Enemy!
moveCharacter(warrior, 5, 0);
moveCharacter(goblin, -3, 2);
๐๏ธ Building Complex Hierarchies
Letโs create a more elaborate example with multiple levels:
// ๐ข E-commerce type hierarchy
interface Product {
id: string;
name: string;
price: number;
description: string;
inStock: boolean;
}
interface PhysicalProduct extends Product {
weight: number;
dimensions: {
length: number;
width: number;
height: number;
};
shippingCost: number;
}
interface DigitalProduct extends Product {
downloadUrl: string;
fileSize: number;
format: string;
}
// ๐ฑ Electronics extend PhysicalProduct
interface Electronics extends PhysicalProduct {
brand: string;
warranty: number; // months
powerRequirements: string;
}
interface Smartphone extends Electronics {
screenSize: number;
camera: {
megapixels: number;
features: string[];
};
storage: number; // GB
operatingSystem: 'iOS' | 'Android' | 'Other';
connectivity: string[];
}
// ๐ Books can be physical or digital
interface Book extends Product {
author: string;
isbn: string;
pages: number;
genre: string[];
publisher: string;
}
interface PhysicalBook extends Book, PhysicalProduct {
coverType: 'hardcover' | 'paperback';
}
interface EBook extends Book, DigitalProduct {
hasAudioVersion: boolean;
supportedDevices: string[];
}
// โ
Create complex products
const iPhone: Smartphone = {
// Product properties
id: 'prod_iphone_15',
name: 'iPhone 15 Pro',
price: 999,
description: 'Latest flagship smartphone',
inStock: true,
// PhysicalProduct properties
weight: 0.187,
dimensions: { length: 146.6, width: 70.6, height: 8.25 },
shippingCost: 9.99,
// Electronics properties
brand: 'Apple',
warranty: 12,
powerRequirements: 'USB-C charging',
// Smartphone specific
screenSize: 6.1,
camera: {
megapixels: 48,
features: ['Night mode', 'ProRAW', '4K video', 'Macro']
},
storage: 256,
operatingSystem: 'iOS',
connectivity: ['5G', 'WiFi 6E', 'Bluetooth 5.3', 'NFC']
};
const programmingBook: EBook = {
// Product properties
id: 'book_ts_mastery',
name: 'TypeScript Mastery',
price: 39.99,
description: 'Complete guide to TypeScript',
inStock: true,
// Book properties
author: 'John Doe',
isbn: '978-1234567890',
pages: 450,
genre: ['Programming', 'Technology', 'Education'],
publisher: 'Tech Press',
// DigitalProduct properties
downloadUrl: 'https://downloads.example.com/ts-mastery.pdf',
fileSize: 15.5, // MB
format: 'PDF',
// EBook specific
hasAudioVersion: true,
supportedDevices: ['Kindle', 'iPad', 'Web Reader', 'Android']
};
// ๐ฏ Functions can work at different hierarchy levels
function calculateShipping(product: PhysicalProduct): number {
const baseRate = 5;
const weightRate = product.weight * 2;
return baseRate + weightRate + product.shippingCost;
}
function applyDiscount(product: Product, percentage: number): number {
return product.price * (1 - percentage / 100);
}
console.log(`๐ฆ Shipping cost for iPhone: $${calculateShipping(iPhone).toFixed(2)}`);
console.log(`๐ฐ EBook price with 20% discount: $${applyDiscount(programmingBook, 20).toFixed(2)}`);
๐ Multiple Interface Extension
๐ Extending Multiple Interfaces
TypeScript allows extending multiple interfaces, combining their properties:
// ๐ฏ Multiple interface composition
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Identifiable {
id: string;
uuid: string;
}
interface Versioned {
version: number;
previousVersions: number[];
}
interface Auditable {
createdBy: string;
lastModifiedBy: string;
changeLog: Array<{
timestamp: Date;
user: string;
changes: string;
}>;
}
// ๐ Document combines multiple interfaces
interface Document extends Identifiable, Timestamped, Versioned, Auditable {
title: string;
content: string;
tags: string[];
status: 'draft' | 'review' | 'published' | 'archived';
}
// ๐ค User profile with multiple concerns
interface ContactInfo {
email: string;
phone?: string;
address?: {
street: string;
city: string;
country: string;
postalCode: string;
};
}
interface SocialMedia {
twitter?: string;
linkedin?: string;
github?: string;
website?: string;
}
interface Preferences {
theme: 'light' | 'dark' | 'auto';
language: string;
notifications: {
email: boolean;
push: boolean;
sms: boolean;
};
}
interface UserProfile extends Identifiable, Timestamped, ContactInfo, SocialMedia, Preferences {
username: string;
displayName: string;
bio: string;
avatar: string;
role: 'user' | 'admin' | 'moderator';
isVerified: boolean;
}
// โ
Create a comprehensive user profile
const userProfile: UserProfile = {
// Identifiable
id: 'usr_123',
uuid: '550e8400-e29b-41d4-a716-446655440000',
// Timestamped
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-06-18'),
// ContactInfo
email: '[email protected]',
phone: '+1-555-0123',
address: {
street: '123 TypeScript Lane',
city: 'Code City',
country: 'Developerland',
postalCode: '12345'
},
// SocialMedia
twitter: '@johndoe',
github: 'johndoe',
linkedin: 'john-doe',
website: 'https://johndoe.dev',
// Preferences
theme: 'dark',
language: 'en-US',
notifications: {
email: true,
push: true,
sms: false
},
// UserProfile specific
username: 'johndoe',
displayName: 'John Doe',
bio: 'TypeScript enthusiast and software architect',
avatar: 'https://avatars.example.com/johndoe.jpg',
role: 'admin',
isVerified: true
};
// ๐ช E-commerce order combining multiple concerns
interface PricingInfo {
subtotal: number;
tax: number;
shipping: number;
discount: number;
total: number;
}
interface PaymentInfo {
method: 'credit_card' | 'paypal' | 'crypto' | 'bank_transfer';
status: 'pending' | 'processing' | 'completed' | 'failed';
transactionId?: string;
paidAt?: Date;
}
interface ShippingInfo {
carrier: string;
trackingNumber?: string;
estimatedDelivery: Date;
shippingAddress: {
recipient: string;
street: string;
city: string;
state: string;
postalCode: string;
country: string;
};
}
interface Order extends Identifiable, Timestamped, PricingInfo, PaymentInfo, ShippingInfo {
customerId: string;
items: Array<{
productId: string;
quantity: number;
price: number;
}>;
status: 'pending' | 'confirmed' | 'shipped' | 'delivered' | 'cancelled';
notes?: string;
}
๐จ Advanced Extension Patterns
๐ง Generic Interface Extension
Combine generics with extension for ultimate flexibility:
// ๐ฏ Generic base interfaces
interface Entity<T> {
id: T;
metadata: Record<string, any>;
}
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Deletable {
deletedAt?: Date;
isDeleted: boolean;
}
// ๐ Generic repository pattern
interface Repository<T, ID = string> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: ID): Promise<void>;
}
interface CrudRepository<T extends Entity<ID>, ID = string> extends Repository<T, ID> {
update(id: ID, partial: Partial<T>): Promise<T>;
exists(id: ID): Promise<boolean>;
count(): Promise<number>;
}
interface TimestampedRepository<T extends Entity<ID> & Timestamped, ID = string>
extends CrudRepository<T, ID> {
findByDateRange(start: Date, end: Date): Promise<T[]>;
findRecentlyUpdated(limit: number): Promise<T[]>;
}
interface SoftDeleteRepository<T extends Entity<ID> & Deletable, ID = string>
extends CrudRepository<T, ID> {
softDelete(id: ID): Promise<void>;
restore(id: ID): Promise<void>;
findDeleted(): Promise<T[]>;
permanentlyDelete(id: ID): Promise<void>;
}
// ๐ข Domain entity
interface User extends Entity<string>, Timestamped, Deletable {
email: string;
username: string;
passwordHash: string;
roles: string[];
}
// ๐ง Specialized user repository
interface UserRepository extends TimestampedRepository<User>, SoftDeleteRepository<User> {
findByEmail(email: string): Promise<User | null>;
findByUsername(username: string): Promise<User | null>;
findByRole(role: string): Promise<User[]>;
updateLastLogin(userId: string): Promise<void>;
}
// โ
Implementation example
class MongoUserRepository implements UserRepository {
async findById(id: string): Promise<User | null> {
console.log(`๐ Finding user by ID: ${id}`);
// MongoDB implementation
return null;
}
async findAll(): Promise<User[]> {
console.log('๐ Finding all users');
return [];
}
async save(user: User): Promise<User> {
console.log(`๐พ Saving user: ${user.username}`);
return user;
}
async delete(id: string): Promise<void> {
console.log(`๐๏ธ Deleting user: ${id}`);
}
async update(id: string, partial: Partial<User>): Promise<User> {
console.log(`๐ Updating user: ${id}`);
// Implementation
return {} as User;
}
async exists(id: string): Promise<boolean> {
console.log(`โ Checking if user exists: ${id}`);
return true;
}
async count(): Promise<number> {
console.log('๐ข Counting users');
return 42;
}
async findByDateRange(start: Date, end: Date): Promise<User[]> {
console.log(`๐
Finding users between ${start} and ${end}`);
return [];
}
async findRecentlyUpdated(limit: number): Promise<User[]> {
console.log(`๐ Finding ${limit} recently updated users`);
return [];
}
async softDelete(id: string): Promise<void> {
console.log(`๐๏ธ Soft deleting user: ${id}`);
}
async restore(id: string): Promise<void> {
console.log(`โป๏ธ Restoring user: ${id}`);
}
async findDeleted(): Promise<User[]> {
console.log('๐๏ธ Finding deleted users');
return [];
}
async permanentlyDelete(id: string): Promise<void> {
console.log(`๐ Permanently deleting user: ${id}`);
}
async findByEmail(email: string): Promise<User | null> {
console.log(`๐ง Finding user by email: ${email}`);
return null;
}
async findByUsername(username: string): Promise<User | null> {
console.log(`๐ค Finding user by username: ${username}`);
return null;
}
async findByRole(role: string): Promise<User[]> {
console.log(`๐ฅ Finding users with role: ${role}`);
return [];
}
async updateLastLogin(userId: string): Promise<void> {
console.log(`๐ Updating last login for user: ${userId}`);
}
}
๐ Interface Merging with Extension
TypeScript allows declaration merging with extended interfaces:
// ๐ฎ Game system with extensible interfaces
interface GameEvent {
id: string;
timestamp: Date;
playerId: string;
}
interface GameEvent {
// This gets merged with the above!
eventType: string;
data: unknown;
}
interface DamageEvent extends GameEvent {
eventType: 'damage';
data: {
targetId: string;
amount: number;
damageType: 'physical' | 'magical' | 'true';
isCritical: boolean;
};
}
interface ItemEvent extends GameEvent {
eventType: 'item_pickup' | 'item_drop' | 'item_use';
data: {
itemId: string;
quantity: number;
location: { x: number; y: number; z: number };
};
}
interface QuestEvent extends GameEvent {
eventType: 'quest_start' | 'quest_complete' | 'quest_fail';
data: {
questId: string;
objectives: string[];
rewards?: {
experience: number;
items: string[];
currency: number;
};
};
}
// ๐ญ Event factory
class GameEventFactory {
private eventCounter = 0;
createDamageEvent(
playerId: string,
targetId: string,
amount: number,
damageType: 'physical' | 'magical' | 'true' = 'physical'
): DamageEvent {
return {
id: `evt_${++this.eventCounter}`,
timestamp: new Date(),
playerId,
eventType: 'damage',
data: {
targetId,
amount,
damageType,
isCritical: Math.random() > 0.8
}
};
}
createItemEvent(
playerId: string,
type: 'item_pickup' | 'item_drop' | 'item_use',
itemId: string,
quantity: number,
location: { x: number; y: number; z: number }
): ItemEvent {
return {
id: `evt_${++this.eventCounter}`,
timestamp: new Date(),
playerId,
eventType: type,
data: { itemId, quantity, location }
};
}
createQuestEvent(
playerId: string,
type: 'quest_start' | 'quest_complete' | 'quest_fail',
questId: string,
objectives: string[],
rewards?: QuestEvent['data']['rewards']
): QuestEvent {
return {
id: `evt_${++this.eventCounter}`,
timestamp: new Date(),
playerId,
eventType: type,
data: { questId, objectives, rewards }
};
}
}
// ๐ฏ Event processing
function processGameEvent(event: GameEvent): void {
console.log(`โก Processing ${event.eventType} event for player ${event.playerId}`);
switch (event.eventType) {
case 'damage':
const damageEvent = event as DamageEvent;
console.log(`๐ฅ ${damageEvent.data.amount} ${damageEvent.data.damageType} damage!`);
break;
case 'item_pickup':
const itemEvent = event as ItemEvent;
console.log(`๐ฆ Picked up ${itemEvent.data.quantity}x item ${itemEvent.data.itemId}`);
break;
case 'quest_complete':
const questEvent = event as QuestEvent;
console.log(`๐ Quest ${questEvent.data.questId} completed!`);
if (questEvent.data.rewards) {
console.log(`๐ Rewards: ${questEvent.data.rewards.experience} XP`);
}
break;
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Property Name Conflicts
// โ Wrong - conflicting property types
interface Animal {
name: string;
age: number;
}
interface Robot {
name: string;
age: string; // Different type!
}
// Error: Interface 'Cyborg' cannot simultaneously extend types 'Animal' and 'Robot'
// interface Cyborg extends Animal, Robot {
// batteryLevel: number;
// }
// โ
Solution 1: Use compatible types
interface Animal2 {
name: string;
biologicalAge: number;
}
interface Robot2 {
name: string;
manufacturingDate: string;
}
interface Cyborg extends Animal2, Robot2 {
batteryLevel: number;
}
// โ
Solution 2: Use intersection types for conflicts
type CyborgType = Animal & Robot & {
batteryLevel: number;
age: number | string; // Explicitly handle the conflict
};
๐คฏ Pitfall 2: Deep Extension Chains
// โ Wrong - too deep inheritance
interface A { a: string; }
interface B extends A { b: string; }
interface C extends B { c: string; }
interface D extends C { d: string; }
interface E extends D { e: string; }
interface F extends E { f: string; }
// This gets hard to maintain!
// โ
Better - use composition
interface BaseProps {
id: string;
timestamp: Date;
}
interface UserProps {
username: string;
email: string;
}
interface ProfileProps {
bio: string;
avatar: string;
}
interface AdminProps {
permissions: string[];
adminLevel: number;
}
// Compose what you need
interface AdminUser extends BaseProps, UserProps, ProfileProps, AdminProps {
lastAdminAction: Date;
}
๐ Pitfall 3: Circular Dependencies
// โ Wrong - circular reference
// interface Parent extends Child { // Error!
// children: Child[];
// }
// interface Child extends Parent {
// parent: Parent;
// }
// โ
Correct - use proper hierarchy
interface Person {
id: string;
name: string;
}
interface Parent extends Person {
children: Child[];
}
interface Child extends Person {
parents: Parent[];
school?: string;
}
๐ ๏ธ Best Practices
๐ฏ Extension Guidelines
- Keep It Shallow ๐: Avoid deep inheritance chains
- Compose, Donโt Inherit ๐งฉ: Prefer composition for flexibility
- Single Responsibility ๐ฏ: Each interface should have one purpose
- Meaningful Names ๐: Use descriptive interface names
// ๐ Good practices example
interface Identifiable {
id: string;
}
interface Trackable {
createdAt: Date;
updatedAt: Date;
createdBy: string;
updatedBy: string;
}
interface Publishable {
publishedAt?: Date;
isPublished: boolean;
publishedBy?: string;
}
interface SEO {
metaTitle: string;
metaDescription: string;
keywords: string[];
canonicalUrl?: string;
}
// ๐ Blog post combining concerns
interface BlogPost extends Identifiable, Trackable, Publishable, SEO {
title: string;
slug: string;
content: string;
excerpt: string;
featuredImage?: string;
category: string;
tags: string[];
readTime: number;
viewCount: number;
}
// ๐ฏ Clear, composable, maintainable!
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Component System
Create a flexible UI component type system using interface extension:
๐ Requirements:
- โ Base component interface with common props
- ๐จ Styled component extension
- ๐ฏ Interactive component extension
- ๐ Form component hierarchy
- ๐ง Composite component patterns
๐ Bonus Points:
- Add generic type parameters
- Implement event handling interfaces
- Create specialized input components
๐ก Solution
๐ Click to see solution
// ๐ฏ Base component interfaces
interface BaseComponent {
id?: string;
className?: string;
testId?: string;
'aria-label'?: string;
}
interface StyledComponent {
style?: React.CSSProperties;
theme?: 'light' | 'dark' | 'auto';
variant?: string;
size?: 'small' | 'medium' | 'large';
}
interface InteractiveComponent {
disabled?: boolean;
loading?: boolean;
tabIndex?: number;
role?: string;
}
// ๐จ Layout components
interface LayoutComponent extends BaseComponent, StyledComponent {
children?: React.ReactNode;
padding?: string | number;
margin?: string | number;
}
interface FlexContainer extends LayoutComponent {
direction?: 'row' | 'column';
justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around';
align?: 'start' | 'center' | 'end' | 'stretch';
gap?: string | number;
wrap?: boolean;
}
interface GridContainer extends LayoutComponent {
columns?: number | string;
rows?: number | string;
gap?: string | number;
areas?: string[];
}
// ๐ Button hierarchy
interface ButtonBase extends BaseComponent, StyledComponent, InteractiveComponent {
children: React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
type?: 'button' | 'submit' | 'reset';
}
interface IconButton extends ButtonBase {
icon: React.ReactNode;
iconPosition?: 'left' | 'right';
children?: never; // Icon buttons don't have text children
tooltip?: string;
}
interface ActionButton extends ButtonBase {
variant: 'primary' | 'secondary' | 'danger' | 'success' | 'warning';
fullWidth?: boolean;
icon?: React.ReactNode;
iconPosition?: 'left' | 'right';
}
interface ToggleButton extends ButtonBase {
pressed: boolean;
onToggle: (pressed: boolean) => void;
'aria-pressed': boolean;
}
// ๐ Form components
interface FormField<T = string> extends BaseComponent, InteractiveComponent {
name: string;
value: T;
onChange: (value: T) => void;
error?: string;
required?: boolean;
label?: string;
hint?: string;
}
interface TextInput extends FormField<string> {
type?: 'text' | 'email' | 'password' | 'tel' | 'url';
placeholder?: string;
maxLength?: number;
pattern?: string;
autoComplete?: string;
}
interface NumberInput extends FormField<number> {
min?: number;
max?: number;
step?: number;
placeholder?: string;
}
interface SelectInput<T = string> extends FormField<T> {
options: Array<{
value: T;
label: string;
disabled?: boolean;
group?: string;
}>;
placeholder?: string;
multiple?: boolean;
}
interface CheckboxInput extends FormField<boolean> {
indeterminate?: boolean;
children: React.ReactNode;
}
interface RadioGroup<T = string> extends FormField<T> {
options: Array<{
value: T;
label: string;
disabled?: boolean;
}>;
orientation?: 'horizontal' | 'vertical';
}
interface DatePicker extends FormField<Date | null> {
minDate?: Date;
maxDate?: Date;
disabledDates?: Date[];
format?: string;
showTime?: boolean;
}
// ๐ฏ Complex form component
interface Form extends BaseComponent {
onSubmit: (values: Record<string, any>) => void | Promise<void>;
onReset?: () => void;
children: React.ReactNode;
validationSchema?: any;
initialValues?: Record<string, any>;
}
interface FormSection extends BaseComponent {
title?: string;
description?: string;
collapsible?: boolean;
defaultCollapsed?: boolean;
children: React.ReactNode;
}
// ๐ Data display components
interface TableColumn<T> {
key: keyof T | string;
header: string;
render?: (value: any, row: T) => React.ReactNode;
sortable?: boolean;
width?: string | number;
align?: 'left' | 'center' | 'right';
}
interface Table<T> extends BaseComponent, StyledComponent {
data: T[];
columns: TableColumn<T>[];
onRowClick?: (row: T) => void;
selectable?: boolean;
onSelectionChange?: (selected: T[]) => void;
loading?: boolean;
emptyMessage?: string;
pagination?: {
page: number;
pageSize: number;
total: number;
onPageChange: (page: number) => void;
};
}
// ๐ง Composite component example
interface Card extends LayoutComponent, InteractiveComponent {
hoverable?: boolean;
onClick?: () => void;
}
interface CardHeader extends BaseComponent {
title: string;
subtitle?: string;
action?: React.ReactNode;
avatar?: React.ReactNode;
}
interface CardBody extends LayoutComponent {
scrollable?: boolean;
maxHeight?: string | number;
}
interface CardFooter extends LayoutComponent {
actions?: React.ReactNode[];
align?: 'left' | 'center' | 'right';
}
// ๐จ Modal/Dialog components
interface Modal extends BaseComponent {
open: boolean;
onClose: () => void;
title?: string;
size?: 'small' | 'medium' | 'large' | 'fullscreen';
closeOnEscape?: boolean;
closeOnBackdropClick?: boolean;
showCloseButton?: boolean;
}
interface ConfirmDialog extends Modal {
message: string;
confirmText?: string;
cancelText?: string;
onConfirm: () => void | Promise<void>;
onCancel?: () => void;
variant?: 'info' | 'warning' | 'danger';
}
// ๐ Implementation example
const MyAwesomeForm: React.FC = () => {
const handleSubmit = (values: Record<string, any>) => {
console.log('๐ Form submitted:', values);
};
return (
<Form
id="user-registration"
onSubmit={handleSubmit}
className="registration-form"
>
<FormSection title="Personal Information" description="Tell us about yourself">
<FlexContainer direction="column" gap={16}>
<TextInput
name="fullName"
label="Full Name"
value=""
onChange={(value) => console.log('Name:', value)}
required
placeholder="John Doe"
/>
<TextInput
name="email"
label="Email"
type="email"
value=""
onChange={(value) => console.log('Email:', value)}
required
error="Please enter a valid email"
/>
<DatePicker
name="birthDate"
label="Date of Birth"
value={null}
onChange={(value) => console.log('Birth date:', value)}
maxDate={new Date()}
/>
</FlexContainer>
</FormSection>
<FormSection title="Preferences" collapsible defaultCollapsed>
<RadioGroup
name="theme"
label="Preferred Theme"
value="light"
options={[
{ value: 'light', label: 'โ๏ธ Light' },
{ value: 'dark', label: '๐ Dark' },
{ value: 'auto', label: '๐ค Auto' }
]}
onChange={(value) => console.log('Theme:', value)}
orientation="horizontal"
/>
<CheckboxInput
name="newsletter"
value={false}
onChange={(value) => console.log('Newsletter:', value)}
>
Subscribe to our newsletter
</CheckboxInput>
</FormSection>
<FlexContainer justify="end" gap={8} margin="16px 0 0 0">
<ActionButton
variant="secondary"
type="reset"
onClick={() => console.log('Form reset')}
>
Cancel
</ActionButton>
<ActionButton
variant="primary"
type="submit"
icon={<span>โ
</span>}
>
Register
</ActionButton>
</FlexContainer>
</Form>
);
};
// ๐๏ธ Type-safe component factory
class ComponentFactory {
static createButton<T extends ButtonBase>(
type: 'action' | 'icon' | 'toggle',
props: T
): T {
console.log(`๐จ Creating ${type} button`);
return {
...props,
'aria-label': props['aria-label'] || props.children?.toString()
};
}
static createFormField<T extends FormField>(
fieldType: string,
props: Omit<T, 'onChange'> & { onChange?: (value: any) => void }
): T {
const fieldProps = {
...props,
onChange: props.onChange || (() => {}),
'aria-required': props.required,
'aria-invalid': !!props.error
};
console.log(`๐ Creating ${fieldType} field: ${props.name}`);
return fieldProps as T;
}
}
// Usage
const submitButton = ComponentFactory.createButton('action', {
variant: 'primary',
children: 'Submit',
onClick: () => console.log('Submitted!'),
size: 'large'
} as ActionButton);
const emailField = ComponentFactory.createFormField('text', {
name: 'email',
type: 'email',
label: 'Email Address',
value: '',
required: true,
placeholder: '[email protected]'
} as TextInput);
๐ Key Takeaways
You now understand how to build complex type hierarchies with interface extension! Hereโs what youโve learned:
- โ Single extension creates simple hierarchies ๐ฏ
- โ Multiple extension combines interfaces powerfully ๐
- โ Generic extension provides ultimate flexibility ๐งฌ
- โ Composition patterns keep code maintainable ๐งฉ
- โ Interface merging extends existing types ๐
Remember: Good interface design is like good architecture - it should be solid, flexible, and beautiful! ๐๏ธ
๐ค Next Steps
Congratulations! ๐ Youโve mastered interface extension in TypeScript!
Hereโs what to do next:
- ๐ป Practice with the component system exercise above
- ๐๏ธ Refactor existing code to use interface extension
- ๐ Move on to our next tutorial: Implementing Multiple Interfaces: Composition over Inheritance
- ๐ Build your own type hierarchies for real projects!
Remember: The best type systems grow organically through thoughtful extension and composition. Keep building! ๐
Happy coding! ๐๐โจ