Prerequisites
- Understanding of mapped types ๐ฏ
- Knowledge of template literal types ๐
- Familiarity with conditional types โก
What you'll learn
- Master key remapping syntax and patterns ๐๏ธ
- Transform property names with precision ๐ง
- Build advanced type transformation utilities ๐จ
- Create elegant API design patterns ๐
๐ฏ Introduction
Welcome to the sophisticated world of key remapping! ๐ If mapped types are like factories for transforming values, then key remapping is like having a magical name generator ๐ท๏ธ that can transform property names according to any rule you can imagine!
Youโre about to discover one of TypeScript 4.1โs most powerful features - the ability to not just transform property values, but to completely rename properties during the transformation process. Whether youโre building API adapters ๐, creating elegant utility types ๐ ๏ธ, or designing sophisticated type-safe patterns ๐งโโ๏ธ, key remapping will give you unprecedented control over type structure.
By the end of this tutorial, youโll be renaming and reshaping type properties with the precision of a master architect! โจ Letโs unlock this transformative power! ๐
๐ Understanding Key Remapping
๐ค What is Key Remapping?
Key remapping allows you to change property names while creating mapped types. Think of it as a translator ๐ฃ๏ธ that can take any property name and transform it into something completely different based on your rules.
The syntax uses the as
keyword:
type RemappedType<T> = {
[K in keyof T as NewKeyName]: T[K]
}
This reads as: โFor each key K in type T, create a new property named NewKeyName with the same value typeโ
๐จ Your First Key Remapping
Letโs start with simple examples:
// ๐ฏ Original interface
interface User {
id: number;
name: string;
email: string;
age: number;
}
// ๐ช Add prefix to all property names
type PrefixedUser<T> = {
[K in keyof T as `user_${string & K}`]: T[K];
};
// ๐งช Testing our remapping
type DatabaseUser = PrefixedUser<User>;
// Result: {
// user_id: number;
// user_name: string;
// user_email: string;
// user_age: number;
// }
// ๐ Transform to getter method names
type GetterMethods<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// โจ Testing getter transformation
type UserGetters = GetterMethods<User>;
// Result: {
// getId: () => number;
// getName: () => string;
// getEmail: () => string;
// getAge: () => number;
// }
๐ก The Magic: Key remapping lets you create completely new property names while preserving the value types!
๐ Breaking Down the Syntax
type RemappedType<T> = {
[K in keyof T as TransformKey<K>]: T[K]
// ^ ^ ^ ^ ^ ^
// | | | | | โโโ Original value type
// | | | | โโโโโโโโ New key name
// | | | โโโโโโโโโโโโโโโโโโโโโโโ "as" keyword for remapping
// | | โโโโโโโโโโโโโโโโโโโโโโโโโโโ All keys of type T
// | โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ "in" operator for iteration
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ The property key variable
}
๐ง Key Remapping Patterns
๐ Essential Transformation Recipes
Letโs explore the most powerful key remapping patterns:
// ๐จ Convert snake_case to camelCase
type SnakeToCamel<S extends string> = S extends `${infer T}_${infer U}`
? `${T}${Capitalize<SnakeToCamel<U>>}`
: S;
type CamelCaseProps<T> = {
[K in keyof T as SnakeToCamel<string & K>]: T[K];
};
// ๐งช Testing snake_case conversion
interface ApiResponse {
user_id: number;
full_name: string;
email_address: string;
created_at: Date;
is_active: boolean;
}
type FrontendUser = CamelCaseProps<ApiResponse>;
// Result: {
// userId: number;
// fullName: string;
// emailAddress: string;
// createdAt: Date;
// isActive: boolean;
// }
// ๐ Create event handler names
type EventHandlers<T> = {
[K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void;
};
// ๐ฎ Testing event handler generation
interface FormData {
username: string;
password: string;
rememberMe: boolean;
}
type FormHandlers = EventHandlers<FormData>;
// Result: {
// onUsernameChange: (value: string) => void;
// onPasswordChange: (value: string) => void;
// onRememberMeChange: (value: boolean) => void;
// }
// โจ Filter and rename properties conditionally
type StringPropsOnly<T> = {
[K in keyof T as T[K] extends string ? `str_${string & K}` : never]: T[K];
};
// ๐งช Testing conditional filtering
interface MixedProps {
name: string; // Will be included as str_name
age: number; // Will be excluded
description: string; // Will be included as str_description
active: boolean; // Will be excluded
}
type OnlyStringProps = StringPropsOnly<MixedProps>;
// Result: {
// str_name: string;
// str_description: string;
// }
๐ฏ Advanced Remapping Techniques
// ๐จ Create both getter and setter methods
type AccessorMethods<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
} & {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
// ๐งช Testing accessor generation
type UserAccessors = AccessorMethods<User>;
// Result: Complete getter/setter interface!
// ๐ Transform property names based on value types
type TypePrefixedProps<T> = {
[K in keyof T as T[K] extends string
? `str_${string & K}`
: T[K] extends number
? `num_${string & K}`
: T[K] extends boolean
? `bool_${string & K}`
: `obj_${string & K}`
]: T[K];
};
// ๐ฎ Testing type-based prefixing
interface ComplexObject {
title: string;
count: number;
enabled: boolean;
metadata: object;
}
type PrefixedComplex = TypePrefixedProps<ComplexObject>;
// Result: {
// str_title: string;
// num_count: number;
// bool_enabled: boolean;
// obj_metadata: object;
// }
// โจ Create validation method names
type ValidationMethods<T> = {
[K in keyof T as `validate${Capitalize<string & K>}`]: (value: T[K]) => boolean;
} & {
[K in keyof T as `${string & K}Error`]: string | null;
};
type UserValidation = ValidationMethods<User>;
// Result: Both validation methods and error properties!
๐ก Practical Examples
๐ Example 1: Database-to-Frontend Mapper
Letโs build a sophisticated data transformation system:
// ๐ฏ Database schema with snake_case naming
interface DatabaseUser {
user_id: number;
first_name: string;
last_name: string;
email_address: string;
phone_number: string | null;
date_of_birth: string; // ISO string
created_at: string; // ISO timestamp
updated_at: string; // ISO timestamp
is_active: boolean;
profile_picture_url: string | null;
}
interface DatabasePost {
post_id: number;
author_id: number;
post_title: string;
post_content: string;
published_at: string | null;
view_count: number;
like_count: number;
is_featured: boolean;
created_at: string;
updated_at: string;
}
// ๐ Advanced snake_case to camelCase conversion
type SnakeToCamelCase<S extends string> =
S extends `${infer T}_${infer U}`
? `${T}${Capitalize<SnakeToCamelCase<U>>}`
: S;
// ๐จ Transform database types to frontend types
type DatabaseToFrontend<T> = {
[K in keyof T as SnakeToCamelCase<string & K>]:
// Transform date strings to Date objects
T[K] extends string
? K extends `${string}_at` | `date_of_birth` | `published_at`
? Date | (T[K] extends null ? null : never)
: T[K]
: T[K];
};
// โจ Create update payload types (exclude auto-generated fields)
type CreateUpdatePayload<T> = {
[K in keyof T as K extends 'created_at' | 'updated_at'
? never
: SnakeToCamelCase<string & K>
]: T[K] extends string
? K extends `${string}_at` | `date_of_birth`
? Date | (T[K] extends null ? null : never)
: T[K]
: T[K];
};
// ๐งช Testing transformations
type FrontendUser = DatabaseToFrontend<DatabaseUser>;
// Result: {
// userId: number;
// firstName: string;
// lastName: string;
// emailAddress: string;
// phoneNumber: string | null;
// dateOfBirth: Date;
// createdAt: Date;
// updatedAt: Date;
// isActive: boolean;
// profilePictureUrl: string | null;
// }
type UserUpdatePayload = CreateUpdatePayload<DatabaseUser>;
// Result: Same as above but without createdAt and updatedAt
// ๐ฎ Type-safe data mapper
class DatabaseMapper {
// ๐ Transform user from database to frontend format
transformUser(dbUser: DatabaseUser): FrontendUser {
return {
userId: dbUser.user_id,
firstName: dbUser.first_name,
lastName: dbUser.last_name,
emailAddress: dbUser.email_address,
phoneNumber: dbUser.phone_number,
dateOfBirth: new Date(dbUser.date_of_birth),
createdAt: new Date(dbUser.created_at),
updatedAt: new Date(dbUser.updated_at),
isActive: dbUser.is_active,
profilePictureUrl: dbUser.profile_picture_url
} as FrontendUser;
}
// ๐ฏ Transform update payload to database format
transformUpdatePayload(updateData: UserUpdatePayload): Partial<DatabaseUser> {
const dbUpdate: Partial<DatabaseUser> = {};
if ('userId' in updateData) dbUpdate.user_id = updateData.userId;
if ('firstName' in updateData) dbUpdate.first_name = updateData.firstName;
if ('lastName' in updateData) dbUpdate.last_name = updateData.lastName;
if ('emailAddress' in updateData) dbUpdate.email_address = updateData.emailAddress;
if ('phoneNumber' in updateData) dbUpdate.phone_number = updateData.phoneNumber;
if ('dateOfBirth' in updateData && updateData.dateOfBirth) {
dbUpdate.date_of_birth = updateData.dateOfBirth.toISOString();
}
if ('isActive' in updateData) dbUpdate.is_active = updateData.isActive;
if ('profilePictureUrl' in updateData) dbUpdate.profile_picture_url = updateData.profilePictureUrl;
return dbUpdate;
}
// โจ Generic transformation method
transformCollection<T, U>(
items: T[],
transformer: (item: T) => U
): U[] {
return items.map(transformer);
}
}
// ๐งช Usage example
const mapper = new DatabaseMapper();
const dbUser: DatabaseUser = {
user_id: 123,
first_name: "Alice",
last_name: "Wonder",
email_address: "[email protected]",
phone_number: "+1-555-0123",
date_of_birth: "1990-05-15T00:00:00Z",
created_at: "2023-01-15T10:30:00Z",
updated_at: "2023-12-01T14:45:00Z",
is_active: true,
profile_picture_url: "https://example.com/avatar.jpg"
};
const frontendUser = mapper.transformUser(dbUser);
console.log("๐ Transformed user:", frontendUser);
// TypeScript knows all property names are camelCase and dates are Date objects!
๐ฎ Example 2: React Component Props Generator
Letโs create a system for generating component prop interfaces:
// ๐ Base component configuration
interface ComponentConfig {
title: string;
description: string;
variant: 'primary' | 'secondary' | 'danger';
size: 'small' | 'medium' | 'large';
disabled: boolean;
loading: boolean;
icon: string | null;
}
interface FormConfig {
placeholder: string;
required: boolean;
validation: string;
errorMessage: string;
helpText: string;
}
// ๐ฏ Generate prop names with different patterns
type ComponentProps<T> = {
// Regular props
[K in keyof T]: T[K];
} & {
// Default value props
[K in keyof T as `default${Capitalize<string & K>}`]?: T[K];
} & {
// Callback props for value changes
[K in keyof T as `on${Capitalize<string & K>}Change`]?: (value: T[K]) => void;
} & {
// Override props for conditional rendering
[K in keyof T as `override${Capitalize<string & K>}`]?: T[K];
};
// ๐ Generate form field props
type FormFieldProps<T> = {
// Field values
[K in keyof T]: T[K];
} & {
// Field change handlers
[K in keyof T as `handle${Capitalize<string & K>}Change`]: (value: T[K]) => void;
} & {
// Field validation functions
[K in keyof T as `validate${Capitalize<string & K>}`]?: (value: T[K]) => string | null;
} & {
// Field error states
[K in keyof T as `${string & K}Error`]?: string;
} & {
// Field touched states
[K in keyof T as `${string & K}Touched`]?: boolean;
};
// ๐จ Create theme props for styling
type ThemeProps<T> = {
[K in keyof T as `${string & K}Color`]?: string;
} & {
[K in keyof T as `${string & K}Size`]?: number | string;
} & {
[K in keyof T as `${string & K}Style`]?: React.CSSProperties;
};
// โจ Generate responsive props
type ResponsiveProps<T> = {
[K in keyof T]: T[K];
} & {
[K in keyof T as `${string & K}Sm`]?: T[K]; // Small screens
} & {
[K in keyof T as `${string & K}Md`]?: T[K]; // Medium screens
} & {
[K in keyof T as `${string & K}Lg`]?: T[K]; // Large screens
} & {
[K in keyof T as `${string & K}Xl`]?: T[K]; // Extra large screens
};
// ๐งช Testing component prop generation
type ButtonProps = ComponentProps<ComponentConfig>;
// Result: Huge interface with all variants of props!
type FormFieldInterface = FormFieldProps<FormConfig>;
// Result: Complete form field management interface!
// ๐ฎ React component using generated props
interface BaseButtonConfig {
variant: 'primary' | 'secondary';
size: 'small' | 'large';
disabled: boolean;
}
type GeneratedButtonProps = ComponentProps<BaseButtonConfig> & {
children: React.ReactNode;
onClick?: () => void;
};
// ๐ Smart button component
const SmartButton: React.FC<GeneratedButtonProps> = ({
variant,
size,
disabled,
defaultVariant = 'primary',
defaultSize = 'small',
defaultDisabled = false,
onVariantChange,
onSizeChange,
onDisabledChange,
overrideVariant,
overrideSize,
overrideDisabled,
children,
onClick
}) => {
// Component logic using all the generated props
const actualVariant = overrideVariant ?? variant ?? defaultVariant;
const actualSize = overrideSize ?? size ?? defaultSize;
const actualDisabled = overrideDisabled ?? disabled ?? defaultDisabled;
return (
<button
className={`btn btn-${actualVariant} btn-${actualSize}`}
disabled={actualDisabled}
onClick={onClick}
>
{children}
</button>
);
};
// ๐งช Usage with full type safety
const App = () => {
return (
<SmartButton
variant="primary"
size="large"
disabled={false}
defaultVariant="secondary" // TypeScript knows this exists!
onVariantChange={(newVariant) => {
console.log("Variant changed:", newVariant);
}}
overrideSize="small" // TypeScript knows this exists!
>
Click me! ๐
</SmartButton>
);
};
๐ Advanced Key Remapping Techniques
๐งโโ๏ธ Conditional Key Filtering
// ๐จ Keep only certain types of properties
type FilterByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
// ๐งช Testing type filtering
interface MixedInterface {
name: string;
age: number;
active: boolean;
callback: () => void;
data: object;
}
type OnlyFunctions = FilterByType<MixedInterface, Function>;
// Result: { callback: () => void }
type OnlyPrimitives = FilterByType<MixedInterface, string | number | boolean>;
// Result: { name: string; age: number; active: boolean }
// ๐ Remove properties by pattern
type OmitByPattern<T, Pattern extends string> = {
[K in keyof T as K extends `${Pattern}${string}` ? never : K]: T[K];
};
// ๐ฎ Testing pattern filtering
interface ComponentWithPrivates {
_internalState: any;
_privateMethod: () => void;
publicProp: string;
publicMethod: () => string;
}
type PublicOnly = OmitByPattern<ComponentWithPrivates, '_'>;
// Result: { publicProp: string; publicMethod: () => string }
๐๏ธ Complex Key Transformations
// ๐ฏ Create pluralized collection methods
type CollectionMethods<T> = {
[K in keyof T as `get${Capitalize<string & K>}s`]: () => T[K][];
} & {
[K in keyof T as `add${Capitalize<string & K>}`]: (item: T[K]) => void;
} & {
[K in keyof T as `remove${Capitalize<string & K>}`]: (item: T[K]) => void;
} & {
[K in keyof T as `clear${Capitalize<string & K>}s`]: () => void;
};
// ๐งช Testing collection methods
interface EntityTypes {
user: { id: number; name: string };
post: { id: number; title: string };
comment: { id: number; content: string };
}
type EntityManager = CollectionMethods<EntityTypes>;
// Result: {
// getUsers: () => { id: number; name: string }[];
// addUser: (item: { id: number; name: string }) => void;
// removeUser: (item: { id: number; name: string }) => void;
// clearUsers: () => void;
// getPosts: () => { id: number; title: string }[];
// addPost: (item: { id: number; title: string }) => void;
// // ... and so on for all entity types
// }
// โจ Create nested property accessors
type NestedAccessors<T, Prefix extends string = ''> = {
[K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K] extends object
? NestedAccessors<T[K], `${Prefix}${Capitalize<string & K>}`>
: () => T[K];
};
// ๐ฎ Testing nested accessors
interface ComplexConfig {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
api: {
baseUrl: string;
timeout: number;
};
}
type ConfigAccessors = NestedAccessors<ComplexConfig>;
// Result: Deeply nested accessor methods for all properties!
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Key Type Constraints
// โ Forgetting string constraint
type BadRemapping<T> = {
[K in keyof T as `prefix_${K}`]: T[K]; // Error! K might not be string
};
// โ
Proper string constraint
type GoodRemapping<T> = {
[K in keyof T as `prefix_${string & K}`]: T[K]; // Works correctly
};
// โ
Alternative with conditional
type SafeRemapping<T> = {
[K in keyof T as K extends string ? `prefix_${K}` : never]: T[K];
};
๐คฏ Pitfall 2: Never Values Disappearing
// โ Properties becoming never disappear
type FilterProblematic<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface TestInterface {
name: string; // Kept
age: number; // Becomes never, disappears!
active: boolean; // Becomes never, disappears!
}
type Filtered = FilterProblematic<TestInterface>;
// Result: { name: string } - other properties are gone!
// โ
This is actually the intended behavior for filtering
// If you want to keep all properties, don't use conditional keys:
type KeepAllProps<T> = {
[K in keyof T]: T[K] extends string ? T[K] : `converted_${string}`;
};
๐ Pitfall 3: Template Literal Complexity
// โ Overly complex template literals
type OverlyComplex<T> = {
[K in keyof T as `${string & K}_${T[K] extends string ? 'str' : T[K] extends number ? 'num' : 'other'}_${string & K}_property`]: T[K];
};
// โ
Break down complex transformations
type GetTypeSuffix<T> = T extends string ? 'str' : T extends number ? 'num' : 'other';
type CleanerApproach<T> = {
[K in keyof T as `${string & K}_${GetTypeSuffix<T[K]>}_property`]: T[K];
};
๐ ๏ธ Best Practices
- ๐ฏ Use String Constraints: Always use
string & K
for template literals - ๐ Break Down Complex Logic: Use helper types for readability
- ๐ก๏ธ Test Edge Cases: Consider
never
,unknown
, and union types - ๐จ Be Consistent: Follow naming conventions in your remapping patterns
- โจ Document Your Patterns: Complex remapping can be hard to understand later
- ๐ Consider Performance: Very complex remapping can slow down type checking
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Type-Safe State Management System
Create a sophisticated state management system using key remapping:
๐ Requirements:
- โ Generate action creators from state interface
- ๐ง Create reducer methods for each state property
- ๐จ Build selector functions with memoization types
- ๐ Type-safe subscription system
- โจ Middleware support with type safety!
๐ Bonus Points:
- Add time-travel debugging types
- Create dev tools integration
- Build persistence layer types
๐ก Solution
๐ Click to see solution
// ๐ฏ Application state interface
interface AppState {
user: {
id: number;
name: string;
email: string;
preferences: {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
};
} | null;
posts: Array<{
id: number;
title: string;
content: string;
author: string;
published: boolean;
}>;
ui: {
loading: boolean;
error: string | null;
currentPage: string;
sidebarOpen: boolean;
};
settings: {
apiUrl: string;
timeout: number;
retryAttempts: number;
};
}
// ๐ Generate action creators
type ActionCreators<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (payload: T[K]) => {
type: `SET_${Uppercase<string & K>}`;
payload: T[K];
};
} & {
[K in keyof T as `update${Capitalize<string & K>}`]: (updater: (current: T[K]) => T[K]) => {
type: `UPDATE_${Uppercase<string & K>}`;
updater: (current: T[K]) => T[K];
};
} & {
[K in keyof T as `reset${Capitalize<string & K>}`]: () => {
type: `RESET_${Uppercase<string & K>}`;
};
};
// ๐จ Generate selectors
type Selectors<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: (state: T) => T[K];
} & {
[K in keyof T as `use${Capitalize<string & K>}`]: () => T[K]; // React hooks
};
// โจ Generate action types
type ActionTypes<T> = {
[K in keyof T as `SET_${Uppercase<string & K>}`]: `SET_${Uppercase<string & K>}`;
} & {
[K in keyof T as `UPDATE_${Uppercase<string & K>}`]: `UPDATE_${Uppercase<string & K>}`;
} & {
[K in keyof T as `RESET_${Uppercase<string & K>}`]: `RESET_${Uppercase<string & K>}`;
};
// ๐งช Generate all action objects
type Actions<T> = {
[K in keyof ActionCreators<T>]: ReturnType<ActionCreators<T>[K]>;
}[keyof ActionCreators<T>];
// ๐ฎ Type-safe store implementation
class TypeSafeStore<T extends Record<string, any>> {
private state: T;
private listeners: Array<(state: T) => void> = [];
private middlewares: Array<(action: Actions<T>, state: T) => Actions<T>> = [];
constructor(initialState: T) {
this.state = initialState;
}
// ๐ Generate action creators dynamically
createActions(): ActionCreators<T> {
const actions = {} as ActionCreators<T>;
for (const key in this.state) {
const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
// Set action
const setKey = `set${capitalizedKey}` as keyof ActionCreators<T>;
(actions as any)[setKey] = (payload: T[typeof key]) => ({
type: `SET_${key.toUpperCase()}`,
payload
});
// Update action
const updateKey = `update${capitalizedKey}` as keyof ActionCreators<T>;
(actions as any)[updateKey] = (updater: (current: T[typeof key]) => T[typeof key]) => ({
type: `UPDATE_${key.toUpperCase()}`,
updater
});
// Reset action
const resetKey = `reset${capitalizedKey}` as keyof ActionCreators<T>;
(actions as any)[resetKey] = () => ({
type: `RESET_${key.toUpperCase()}`
});
}
return actions;
}
// ๐ฏ Generate selectors dynamically
createSelectors(): Selectors<T> {
const selectors = {} as Selectors<T>;
for (const key in this.state) {
const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
// Get selector
const getKey = `get${capitalizedKey}` as keyof Selectors<T>;
(selectors as any)[getKey] = (state: T) => state[key];
// React hook selector
const useKey = `use${capitalizedKey}` as keyof Selectors<T>;
(selectors as any)[useKey] = () => this.state[key];
}
return selectors;
}
// โจ Dispatch actions with middleware
dispatch(action: Actions<T>): void {
// Apply middlewares
let processedAction = action;
for (const middleware of this.middlewares) {
processedAction = middleware(processedAction, this.state);
}
// Update state based on action type
const actionType = processedAction.type;
const key = actionType.split('_')[1]?.toLowerCase();
if (!key || !(key in this.state)) return;
if (actionType.startsWith('SET_')) {
this.state = {
...this.state,
[key]: (processedAction as any).payload
};
} else if (actionType.startsWith('UPDATE_')) {
this.state = {
...this.state,
[key]: (processedAction as any).updater(this.state[key as keyof T])
};
} else if (actionType.startsWith('RESET_')) {
// Would need initial state reference for reset
console.log(`Resetting ${key}`);
}
// Notify listeners
this.listeners.forEach(listener => listener(this.state));
}
// ๐ฎ Subscribe to state changes
subscribe(listener: (state: T) => void): () => void {
this.listeners.push(listener);
return () => {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
}
// ๐ Add middleware
use(middleware: (action: Actions<T>, state: T) => Actions<T>): void {
this.middlewares.push(middleware);
}
// โจ Get current state
getState(): T {
return { ...this.state };
}
}
// ๐งช Usage example
const store = new TypeSafeStore<AppState>({
user: null,
posts: [],
ui: {
loading: false,
error: null,
currentPage: 'home',
sidebarOpen: false
},
settings: {
apiUrl: 'https://api.example.com',
timeout: 5000,
retryAttempts: 3
}
});
// ๐ฏ Create type-safe actions and selectors
const actions = store.createActions();
const selectors = store.createSelectors();
// โ
All of these are fully type-safe!
const setUserAction = actions.setUser({
id: 1,
name: "Alice",
email: "[email protected]",
preferences: {
theme: 'dark',
language: 'en',
notifications: true
}
});
const updateUIAction = actions.updateUi(current => ({
...current,
loading: true,
currentPage: 'profile'
}));
// ๐ Dispatch actions
store.dispatch(setUserAction);
store.dispatch(updateUIAction);
// ๐ฎ Use selectors
const currentUser = selectors.getUser(store.getState());
const uiState = selectors.getUi(store.getState());
console.log("๐ Type-safe state management!", { currentUser, uiState });
// โจ Subscribe to changes
const unsubscribe = store.subscribe((newState) => {
console.log("๐ State updated:", newState);
});
// ๐งช Middleware example
store.use((action, state) => {
console.log(`๐ Action dispatched: ${action.type}`);
return action; // Could transform action here
});
console.log("๐ฏ Type-safe store setup complete!");
๐ Key Takeaways
Youโve mastered key remapping! Hereโs what you can now do:
- โ Transform property names with surgical precision using advanced patterns ๐ช
- โ Build sophisticated type utilities that reshape entire interfaces ๐ก๏ธ
- โ Create elegant API design patterns with perfect type safety ๐ฏ
- โ Filter and transform properties conditionally like a pro ๐
- โ Design scalable type systems that adapt to any structure ๐
Remember: Key remapping is like having a master key ๐๏ธ that can unlock and reshape any type structure according to your vision!
๐ค Next Steps
Congratulations! ๐ Youโve conquered key remapping in mapped types!
Hereโs what to explore next:
- ๐ป Practice with the state management exercise above
- ๐๏ธ Build your own key remapping utilities for real projects
- ๐ Move on to our next tutorial: Template Literal Types: String Manipulation at Type Level
- ๐ Share your key remapping patterns with the TypeScript community!
You now possess one of TypeScriptโs most sophisticated transformation tools. Use it to create type systems that are both powerful and elegant. Remember - every advanced TypeScript architect started with curiosity about types. Keep experimenting, keep building, and most importantly, have fun reshaping the type world! ๐โจ
Happy key remapping! ๐๐๏ธโจ