Initial commit
This commit is contained in:
168
skills/using-generics/references/advanced-patterns.md
Normal file
168
skills/using-generics/references/advanced-patterns.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Advanced Generic Patterns
|
||||
|
||||
## Recursive Generics
|
||||
|
||||
Use recursive generics to apply transformations deeply through nested object structures:
|
||||
|
||||
```typescript
|
||||
type DeepReadonly<T> = {
|
||||
readonly [P in keyof T]: T[P] extends object
|
||||
? DeepReadonly<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
|
||||
interface Config {
|
||||
database: {
|
||||
host: string;
|
||||
credentials: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const config: DeepReadonly<Config> = {
|
||||
database: {
|
||||
host: "localhost",
|
||||
credentials: {
|
||||
username: "admin",
|
||||
password: "secret"
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Variadic Tuple Types
|
||||
|
||||
TypeScript 4.0+ supports variadic tuple types for precise array concatenation:
|
||||
|
||||
```typescript
|
||||
function concat<T extends readonly unknown[], U extends readonly unknown[]>(
|
||||
arr1: T,
|
||||
arr2: U
|
||||
): [...T, ...U] {
|
||||
return [...arr1, ...arr2];
|
||||
}
|
||||
|
||||
const result = concat([1, 2], ["a", "b"]);
|
||||
|
||||
function curry<T extends unknown[], U extends unknown[], R>(
|
||||
fn: (...args: [...T, ...U]) => R,
|
||||
...first: T
|
||||
): (...args: U) => R {
|
||||
return (...rest: U) => fn(...first, ...rest);
|
||||
}
|
||||
```
|
||||
|
||||
## Template Literal Types
|
||||
|
||||
Combine string literals with generics for type-safe string manipulation:
|
||||
|
||||
```typescript
|
||||
type EventName<T extends string> = `on${Capitalize<T>}`;
|
||||
|
||||
type ClickEvent = EventName<"click">;
|
||||
type HoverEvent = EventName<"hover">;
|
||||
|
||||
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
|
||||
type Endpoint<M extends HTTPMethod, P extends string> = `${M} ${P}`;
|
||||
|
||||
type UserEndpoint = Endpoint<"GET", "/users/:id">;
|
||||
```
|
||||
|
||||
## Branded Types
|
||||
|
||||
Create nominal types in TypeScript's structural type system:
|
||||
|
||||
```typescript
|
||||
type Brand<K, T> = K & { __brand: T };
|
||||
|
||||
type UserId = Brand<string, "UserId">;
|
||||
type EmailAddress = Brand<string, "Email">;
|
||||
type OrderId = Brand<string, "OrderId">;
|
||||
|
||||
function sendEmail(to: EmailAddress, from: EmailAddress): void {
|
||||
console.log(`Sending email from ${from} to ${to}`);
|
||||
}
|
||||
|
||||
function getUser(id: UserId): void {
|
||||
console.log(`Fetching user ${id}`);
|
||||
}
|
||||
|
||||
const email = "user@example.com" as EmailAddress;
|
||||
const userId = "user-123" as UserId;
|
||||
|
||||
sendEmail(email, email);
|
||||
getUser(userId);
|
||||
```
|
||||
|
||||
## Distributive Conditional Types
|
||||
|
||||
Conditional types distribute over unions when the checked type is a naked type parameter:
|
||||
|
||||
```typescript
|
||||
type ToArray<T> = T extends any ? T[] : never;
|
||||
|
||||
type Nums = ToArray<number | string>;
|
||||
|
||||
type NonNullable<T> = T extends null | undefined ? never : T;
|
||||
|
||||
type SafeString = NonNullable<string | null | undefined>;
|
||||
```
|
||||
|
||||
## Mapped Type Modifiers
|
||||
|
||||
Use `+`, `-`, `readonly`, and `?` modifiers in mapped types:
|
||||
|
||||
```typescript
|
||||
type Mutable<T> = {
|
||||
-readonly [P in keyof T]: T[P];
|
||||
};
|
||||
|
||||
type Required<T> = {
|
||||
[P in keyof T]-?: T[P];
|
||||
};
|
||||
|
||||
type ReadonlyPartial<T> = {
|
||||
+readonly [P in keyof T]+?: T[P];
|
||||
};
|
||||
```
|
||||
|
||||
## Inference in Conditional Types
|
||||
|
||||
Use `infer` to extract types within conditional types:
|
||||
|
||||
```typescript
|
||||
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
|
||||
|
||||
type ArrayElement<T> = T extends (infer E)[] ? E : never;
|
||||
|
||||
type PromiseValue<T> = T extends Promise<infer V> ? V : T;
|
||||
|
||||
type FirstParameter<T> = T extends (first: infer F, ...args: any[]) => any
|
||||
? F
|
||||
: never;
|
||||
```
|
||||
|
||||
## Higher-Kinded Types (Simulation)
|
||||
|
||||
While TypeScript doesn't have true HKTs, you can simulate them:
|
||||
|
||||
```typescript
|
||||
interface Functor<F> {
|
||||
map<A, B>(fa: HKT<F, A>, f: (a: A) => B): HKT<F, B>;
|
||||
}
|
||||
|
||||
interface HKT<F, A> {
|
||||
_F: F;
|
||||
_A: A;
|
||||
}
|
||||
|
||||
interface ArrayF {}
|
||||
|
||||
type ArrayHKT<A> = HKT<ArrayF, A> & A[];
|
||||
|
||||
const arrayFunctor: Functor<ArrayF> = {
|
||||
map: (fa, f) => fa.map(f)
|
||||
};
|
||||
```
|
||||
332
skills/using-generics/references/common-patterns.md
Normal file
332
skills/using-generics/references/common-patterns.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# Common Generic Patterns
|
||||
|
||||
## Array Operations
|
||||
|
||||
Generic array utilities maintain type safety while providing reusable functionality:
|
||||
|
||||
```typescript
|
||||
function last<T>(arr: T[]): T | undefined {
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
|
||||
function first<T>(arr: T[]): T | undefined {
|
||||
return arr[0];
|
||||
}
|
||||
|
||||
function chunk<T>(arr: T[], size: number): T[][] {
|
||||
const chunks: T[][] = [];
|
||||
for (let i = 0; i < arr.length; i += size) {
|
||||
chunks.push(arr.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function flatten<T>(arr: T[][]): T[] {
|
||||
return arr.reduce((acc, item) => acc.concat(item), []);
|
||||
}
|
||||
|
||||
function unique<T>(arr: T[]): T[] {
|
||||
return Array.from(new Set(arr));
|
||||
}
|
||||
|
||||
function partition<T>(arr: T[], predicate: (item: T) => boolean): [T[], T[]] {
|
||||
const truthy: T[] = [];
|
||||
const falsy: T[] = [];
|
||||
|
||||
for (const item of arr) {
|
||||
if (predicate(item)) {
|
||||
truthy.push(item);
|
||||
} else {
|
||||
falsy.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return [truthy, falsy];
|
||||
}
|
||||
```
|
||||
|
||||
## Object Utilities
|
||||
|
||||
Type-safe object manipulation:
|
||||
|
||||
```typescript
|
||||
function pick<T extends object, K extends keyof T>(
|
||||
obj: T,
|
||||
...keys: K[]
|
||||
): Pick<T, K> {
|
||||
const result = {} as Pick<T, K>;
|
||||
for (const key of keys) {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function omit<T extends object, K extends keyof T>(
|
||||
obj: T,
|
||||
...keys: K[]
|
||||
): Omit<T, K> {
|
||||
const result = { ...obj };
|
||||
for (const key of keys) {
|
||||
delete result[key];
|
||||
}
|
||||
return result as Omit<T, K>;
|
||||
}
|
||||
|
||||
function keys<T extends object>(obj: T): (keyof T)[] {
|
||||
return Object.keys(obj) as (keyof T)[];
|
||||
}
|
||||
|
||||
function values<T extends object>(obj: T): T[keyof T][] {
|
||||
return Object.values(obj) as T[keyof T][];
|
||||
}
|
||||
|
||||
function entries<T extends object>(obj: T): [keyof T, T[keyof T]][] {
|
||||
return Object.entries(obj) as [keyof T, T[keyof T]][];
|
||||
}
|
||||
|
||||
function mapValues<T extends object, U>(
|
||||
obj: T,
|
||||
fn: (value: T[keyof T], key: keyof T) => U
|
||||
): Record<keyof T, U> {
|
||||
const result = {} as Record<keyof T, U>;
|
||||
|
||||
for (const key in obj) {
|
||||
result[key] = fn(obj[key], key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## Promise Utilities
|
||||
|
||||
Generic async operations:
|
||||
|
||||
```typescript
|
||||
async function retry<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxAttempts: number
|
||||
): Promise<T> {
|
||||
let lastError: Error;
|
||||
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError!;
|
||||
}
|
||||
|
||||
async function timeout<T>(
|
||||
promise: Promise<T>,
|
||||
ms: number
|
||||
): Promise<T> {
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => reject(new Error("Timeout")), ms);
|
||||
});
|
||||
|
||||
return Promise.race([promise, timeoutPromise]);
|
||||
}
|
||||
|
||||
async function sequence<T>(
|
||||
promises: (() => Promise<T>)[]
|
||||
): Promise<T[]> {
|
||||
const results: T[] = [];
|
||||
|
||||
for (const promiseFn of promises) {
|
||||
results.push(await promiseFn());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Class Generics
|
||||
|
||||
Generic classes for reusable data structures:
|
||||
|
||||
```typescript
|
||||
class Container<T> {
|
||||
constructor(private value: T) {}
|
||||
|
||||
getValue(): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
setValue(value: T): void {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
map<U>(fn: (value: T) => U): Container<U> {
|
||||
return new Container(fn(this.value));
|
||||
}
|
||||
|
||||
flatMap<U>(fn: (value: T) => Container<U>): Container<U> {
|
||||
return fn(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
class Result<T, E = Error> {
|
||||
private constructor(
|
||||
private readonly value?: T,
|
||||
private readonly error?: E
|
||||
) {}
|
||||
|
||||
static ok<T, E = Error>(value: T): Result<T, E> {
|
||||
return new Result(value, undefined);
|
||||
}
|
||||
|
||||
static err<T, E = Error>(error: E): Result<T, E> {
|
||||
return new Result(undefined, error);
|
||||
}
|
||||
|
||||
isOk(): this is Result<T, never> {
|
||||
return this.value !== undefined;
|
||||
}
|
||||
|
||||
isErr(): this is Result<never, E> {
|
||||
return this.error !== undefined;
|
||||
}
|
||||
|
||||
unwrap(): T {
|
||||
if (this.error !== undefined) {
|
||||
throw this.error;
|
||||
}
|
||||
return this.value!;
|
||||
}
|
||||
|
||||
map<U>(fn: (value: T) => U): Result<U, E> {
|
||||
if (this.error !== undefined) {
|
||||
return Result.err(this.error);
|
||||
}
|
||||
return Result.ok(fn(this.value!));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Builder Pattern
|
||||
|
||||
Fluent API with type safety:
|
||||
|
||||
```typescript
|
||||
class QueryBuilder<T extends object> {
|
||||
private filters: Array<(item: T) => boolean> = [];
|
||||
private sortFn?: (a: T, b: T) => number;
|
||||
private limitValue?: number;
|
||||
|
||||
where<K extends keyof T>(key: K, value: T[K]): this {
|
||||
this.filters.push(item => item[key] === value);
|
||||
return this;
|
||||
}
|
||||
|
||||
whereFn(predicate: (item: T) => boolean): this {
|
||||
this.filters.push(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
sort<K extends keyof T>(key: K, direction: "asc" | "desc" = "asc"): this {
|
||||
this.sortFn = (a, b) => {
|
||||
const aVal = a[key];
|
||||
const bVal = b[key];
|
||||
|
||||
if (aVal < bVal) return direction === "asc" ? -1 : 1;
|
||||
if (aVal > bVal) return direction === "asc" ? 1 : -1;
|
||||
return 0;
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
limit(n: number): this {
|
||||
this.limitValue = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
execute(data: T[]): T[] {
|
||||
let result = data.filter(item =>
|
||||
this.filters.every(filter => filter(item))
|
||||
);
|
||||
|
||||
if (this.sortFn) {
|
||||
result = result.sort(this.sortFn);
|
||||
}
|
||||
|
||||
if (this.limitValue !== undefined) {
|
||||
result = result.slice(0, this.limitValue);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const users = [
|
||||
{ id: 1, name: "Alice", active: true, age: 30 },
|
||||
{ id: 2, name: "Bob", active: false, age: 25 },
|
||||
{ id: 3, name: "Charlie", active: true, age: 35 }
|
||||
];
|
||||
|
||||
const active = new QueryBuilder<typeof users[0]>()
|
||||
.where("active", true)
|
||||
.sort("age", "desc")
|
||||
.limit(5)
|
||||
.execute(users);
|
||||
```
|
||||
|
||||
## Event Emitter Pattern
|
||||
|
||||
Type-safe event handling:
|
||||
|
||||
```typescript
|
||||
type EventMap = Record<string, any>;
|
||||
|
||||
class TypedEventEmitter<Events extends EventMap> {
|
||||
private listeners: {
|
||||
[K in keyof Events]?: Array<(data: Events[K]) => void>;
|
||||
} = {};
|
||||
|
||||
on<K extends keyof Events>(
|
||||
event: K,
|
||||
listener: (data: Events[K]) => void
|
||||
): this {
|
||||
if (!this.listeners[event]) {
|
||||
this.listeners[event] = [];
|
||||
}
|
||||
this.listeners[event]!.push(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
emit<K extends keyof Events>(event: K, data: Events[K]): void {
|
||||
const listeners = this.listeners[event];
|
||||
if (listeners) {
|
||||
for (const listener of listeners) {
|
||||
listener(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
off<K extends keyof Events>(
|
||||
event: K,
|
||||
listener: (data: Events[K]) => void
|
||||
): this {
|
||||
const listeners = this.listeners[event];
|
||||
if (listeners) {
|
||||
this.listeners[event] = listeners.filter(l => l !== listener);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
interface AppEvents {
|
||||
userLogin: { userId: string; timestamp: number };
|
||||
userLogout: { userId: string };
|
||||
dataUpdated: { id: string; changes: Record<string, unknown> };
|
||||
}
|
||||
|
||||
const emitter = new TypedEventEmitter<AppEvents>();
|
||||
|
||||
emitter.on("userLogin", (data) => {
|
||||
console.log(`User ${data.userId} logged in at ${data.timestamp}`);
|
||||
});
|
||||
```
|
||||
305
skills/using-generics/references/detailed-examples.md
Normal file
305
skills/using-generics/references/detailed-examples.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# Detailed Generic Examples
|
||||
|
||||
## Deep Partial Implementation
|
||||
|
||||
The `DeepPartial` type recursively makes all properties optional, including nested objects:
|
||||
|
||||
```typescript
|
||||
type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
||||
};
|
||||
|
||||
interface DatabaseConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
credentials: {
|
||||
username: string;
|
||||
password: string;
|
||||
ssl: {
|
||||
enabled: boolean;
|
||||
cert: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const partialConfig: DeepPartial<DatabaseConfig> = {
|
||||
host: "localhost",
|
||||
credentials: {
|
||||
username: "admin",
|
||||
ssl: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function mergeConfig(
|
||||
defaults: DatabaseConfig,
|
||||
override: DeepPartial<DatabaseConfig>
|
||||
): DatabaseConfig {
|
||||
return {
|
||||
...defaults,
|
||||
...override,
|
||||
credentials: {
|
||||
...defaults.credentials,
|
||||
...override.credentials,
|
||||
ssl: {
|
||||
...defaults.credentials.ssl,
|
||||
...override.credentials?.ssl
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Conditional Return Types
|
||||
|
||||
Use conditional types to change return types based on input:
|
||||
|
||||
```typescript
|
||||
type ParseResult<T extends "json" | "text"> =
|
||||
T extends "json" ? unknown : string;
|
||||
|
||||
function parse<T extends "json" | "text">(
|
||||
response: Response,
|
||||
type: T
|
||||
): Promise<ParseResult<T>> {
|
||||
if (type === "json") {
|
||||
return response.json() as Promise<ParseResult<T>>;
|
||||
}
|
||||
return response.text() as Promise<ParseResult<T>>;
|
||||
}
|
||||
|
||||
async function example() {
|
||||
const json = await parse(response, "json");
|
||||
|
||||
const text = await parse(response, "text");
|
||||
}
|
||||
|
||||
type QueryResult<T extends boolean> = T extends true
|
||||
? Array<{ id: string; data: unknown }>
|
||||
: { id: string; data: unknown } | undefined;
|
||||
|
||||
function query<Multiple extends boolean = false>(
|
||||
sql: string,
|
||||
multiple?: Multiple
|
||||
): QueryResult<Multiple> {
|
||||
if (multiple) {
|
||||
return [] as QueryResult<Multiple>;
|
||||
}
|
||||
return undefined as QueryResult<Multiple>;
|
||||
}
|
||||
|
||||
const single = query("SELECT * FROM users WHERE id = 1");
|
||||
const many = query("SELECT * FROM users", true);
|
||||
```
|
||||
|
||||
## Filter by Type Pattern
|
||||
|
||||
Extract only properties of a specific type from an object:
|
||||
|
||||
```typescript
|
||||
type FilterByType<T, U> = {
|
||||
[P in keyof T as T[P] extends U ? P : never]: T[P];
|
||||
};
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
age: number;
|
||||
active: boolean;
|
||||
createdAt: Date;
|
||||
score: number;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
type StringProps = FilterByType<User, string>;
|
||||
|
||||
type NumberProps = FilterByType<User, number>;
|
||||
|
||||
type FilterByValueType<T, U> = Pick<
|
||||
T,
|
||||
{
|
||||
[K in keyof T]: T[K] extends U ? K : never;
|
||||
}[keyof T]
|
||||
>;
|
||||
|
||||
type BooleanProps = FilterByValueType<User, boolean>;
|
||||
```
|
||||
|
||||
## Unwrap Nested Promises
|
||||
|
||||
Extract the final resolved type from nested Promises:
|
||||
|
||||
```typescript
|
||||
type Awaited<T> = T extends Promise<infer U>
|
||||
? Awaited<U>
|
||||
: T;
|
||||
|
||||
type A = Awaited<Promise<string>>;
|
||||
|
||||
type B = Awaited<Promise<Promise<number>>>;
|
||||
|
||||
type C = Awaited<Promise<Promise<Promise<boolean>>>>;
|
||||
|
||||
async function deepFetch(): Promise<Promise<{ data: string }>> {
|
||||
return Promise.resolve(
|
||||
Promise.resolve({ data: "nested" })
|
||||
);
|
||||
}
|
||||
|
||||
type DeepFetchResult = Awaited<ReturnType<typeof deepFetch>>;
|
||||
```
|
||||
|
||||
## Extract Function Parameters
|
||||
|
||||
Get parameter types from function signatures:
|
||||
|
||||
```typescript
|
||||
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
|
||||
|
||||
function processUser(id: string, name: string, age: number): void {
|
||||
console.log(id, name, age);
|
||||
}
|
||||
|
||||
type ProcessUserParams = Parameters<typeof processUser>;
|
||||
|
||||
function callWithParams<F extends (...args: any[]) => any>(
|
||||
fn: F,
|
||||
...args: Parameters<F>
|
||||
): ReturnType<F> {
|
||||
return fn(...args);
|
||||
}
|
||||
|
||||
callWithParams(processUser, "123", "Alice", 30);
|
||||
|
||||
type FirstParameter<T> = T extends (first: infer F, ...args: any[]) => any
|
||||
? F
|
||||
: never;
|
||||
|
||||
type FirstParam = FirstParameter<typeof processUser>;
|
||||
```
|
||||
|
||||
## Constructable Types
|
||||
|
||||
Work with classes and constructors generically:
|
||||
|
||||
```typescript
|
||||
interface Constructable<T> {
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
|
||||
function create<T>(Constructor: Constructable<T>): T {
|
||||
return new Constructor();
|
||||
}
|
||||
|
||||
function createWithArgs<T, Args extends any[]>(
|
||||
Constructor: new (...args: Args) => T,
|
||||
...args: Args
|
||||
): T {
|
||||
return new Constructor(...args);
|
||||
}
|
||||
|
||||
class User {
|
||||
constructor(public name: string, public age: number) {}
|
||||
}
|
||||
|
||||
const user1 = create(User);
|
||||
const user2 = createWithArgs(User, "Alice", 30);
|
||||
|
||||
function inject<T>(
|
||||
Constructor: Constructable<T>,
|
||||
dependencies: Map<Constructable<any>, any>
|
||||
): T {
|
||||
const args = getDependencies(Constructor, dependencies);
|
||||
return new Constructor(...args);
|
||||
}
|
||||
```
|
||||
|
||||
## Mapped Type Transformations
|
||||
|
||||
Complex property transformations using mapped types:
|
||||
|
||||
```typescript
|
||||
type Getters<T> = {
|
||||
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
|
||||
};
|
||||
|
||||
interface Person {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
type PersonGetters = Getters<Person>;
|
||||
|
||||
type Setters<T> = {
|
||||
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
|
||||
};
|
||||
|
||||
type PersonSetters = Setters<Person>;
|
||||
|
||||
type ReadonlyKeys<T> = {
|
||||
[K in keyof T]-?: T[K] extends { readonly [key: string]: any }
|
||||
? K
|
||||
: never;
|
||||
}[keyof T];
|
||||
|
||||
type WritableKeys<T> = {
|
||||
[K in keyof T]-?: T[K] extends { readonly [key: string]: any }
|
||||
? never
|
||||
: K;
|
||||
}[keyof T];
|
||||
```
|
||||
|
||||
## Type-Safe Event Handlers
|
||||
|
||||
Generic event handler system with strict typing:
|
||||
|
||||
```typescript
|
||||
interface EventHandlers<T> {
|
||||
onCreate?: (item: T) => void;
|
||||
onUpdate?: (item: T, changes: Partial<T>) => void;
|
||||
onDelete?: (id: string) => void;
|
||||
}
|
||||
|
||||
class Store<T extends { id: string }> {
|
||||
private items = new Map<string, T>();
|
||||
private handlers: EventHandlers<T> = {};
|
||||
|
||||
setHandlers(handlers: EventHandlers<T>): void {
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
create(item: T): void {
|
||||
this.items.set(item.id, item);
|
||||
this.handlers.onCreate?.(item);
|
||||
}
|
||||
|
||||
update(id: string, changes: Partial<T>): void {
|
||||
const item = this.items.get(id);
|
||||
if (item) {
|
||||
const updated = { ...item, ...changes };
|
||||
this.items.set(id, updated);
|
||||
this.handlers.onUpdate?.(updated, changes);
|
||||
}
|
||||
}
|
||||
|
||||
delete(id: string): void {
|
||||
this.items.delete(id);
|
||||
this.handlers.onDelete?.(id);
|
||||
}
|
||||
}
|
||||
|
||||
interface Todo {
|
||||
id: string;
|
||||
title: string;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
const todoStore = new Store<Todo>();
|
||||
|
||||
todoStore.setHandlers({
|
||||
onCreate: (todo) => console.log("Created:", todo.title),
|
||||
onUpdate: (todo, changes) => console.log("Updated:", changes),
|
||||
onDelete: (id) => console.log("Deleted:", id)
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user