Sử dụng nâng cao TypeScript: Khai báo kiểu, Mixin, Decorator và các kỹ thuật tiên tiến

1. Tệp khai báo .d.ts

Khi làm việc với thư viện JavaScript trong TypeScript, bạn cần tệp khai báo để cung cấp định nghĩa kiểu. Các từ khóa phổ biến:

  • declare var: Khai báo biến toàn cục
  • declare function: Khai báo hàm toàn cục
  • declare class: Khai báo lớp toàn cục
  • declare namespace: Khai báo đối tượng có thuộc tính con
declare module 'express' {
  interface Router {
    get(path: string, handler: (req: any, res: any) => void): void;
  }

  interface Application {
    use(path: string, router: any): void;
    listen(port: number, callback?: () => void): void;
  }

  interface Express {
    (): Application;
    Router(): Router;
  }

  const express: Express;
  export default express;
}

// Sử dụng
import express from 'express';
express.Router().get('/', (req, res) => {});
express().listen(3000);

2. Mixin - Kỹ thuật kết hợp hành vi

Mixin cho phép tái sử dụng mã giữa các lớp mà không dùng kế thừa.

interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

class NameProvider {
  name: string = "Anonymous";
}

class AgeProvider {
  age: number = 0;
  increaseAge() { this.age++; }
}

function applyMixins(targetClass: any, providers: any[]) {
  providers.forEach(provider => {
    Object.getOwnPropertyNames(provider.prototype).forEach(prop => {
      if (prop !== 'constructor') {
        targetClass.prototype[prop] = provider.prototype[prop];
      }
    });
  });
}

class Person {
  constructor() {}
}

applyMixins(Person, [NameProvider, AgeProvider]);

const person = new Person();
person.increaseAge(); // Phương thức từ AgeProvider

3. Decorator - Trang trí lớp và thành viên

Decorator là hàm đặc biệt dùng để bổ sung hoặc thay đổi hành vi của lớp, phương thức, thuộc tính.

// Bật experimentalDecorators trong tsconfig.json

// Decorator lớp
function addTimestamp<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    timestamp = Date.now();
  };
}

@addTimestamp
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

// Decorator phương thức
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    return originalMethod.apply(this, args);
  };
}

class Calculator {
  @logMethod
  add(a: number, b: number): number {
    return a + b;
  }
}

4. Proxy và Reflect - Kiểm soát truy cập đối tượng

Proxy cho phép chặn và tùy chỉnh các thao tác trên đối tượng. Reflect cung cấp API chuẩn để thao tác đối tượng.

const createTrackedObject = <T extends object>(obj: T) => {
  return new Proxy(obj, {
    get(target, prop, receiver) {
      console.log(`Đọc thuộc tính: ${String(prop)}`);
      return Reflect.get(target, prop, receiver);
    },
    set(target, prop, value, receiver) {
      console.log(`Gán giá trị ${value} cho ${String(prop)}`);
      return Reflect.set(target, prop, value, receiver);
    },
    deleteProperty(target, prop) {
      console.log(`Xóa thuộc tính: ${String(prop)}`);
      return Reflect.deleteProperty(target, prop);
    }
  });
};

const user = createTrackedObject({ name: "Alice", age: 25 });
user.name; // Ghi log khi đọc
user.age = 26; // Ghi log khi gán

5. Utility Types nâng cao

TypeScript cung cấp các kiểu tiện ích để biến đổi kiểu hiện có:

// Partial - Biến tất cả thuộc tính thành optional
type PartialUser = Partial<{ name: string; email: string; age: number }>;
// Kết quả: { name?: string; email?: string; age?: number }

// Pick - Chọn một số thuộc tính cụ thể
type ContactInfo = Pick<{ name: string; email: string; phone: string }, 'name' | 'email'>;
// Kết quả: { name: string; email: string }

// Readonly - Biến tất cả thuộc tính thành readonly
type ImmutableConfig = Readonly<{ apiUrl: string; timeout: number }>;

// Record - Tạo kiểu với key-value cụ thể
type StringRecord = Record<'success' | 'error' | 'warning', string>;
// Kết quả: { success: string; error: string; warning: string }

6. Infer - Suy luận kiểu thông minh

Từ khóa infer cho phép trích xuất kiểu từ các điều kiện:

// Trích xuất kiểu phần tử mảng
type ElementType<T> = T extends (infer U)[] ? U : T;

type NumArray = ElementType<number[]>; // number
type Str = ElementType<string>; // string

// Trích xuất phần tử đầu tiên của tuple
type FirstElement<T extends any[]> = T extends [infer F, ...any[]] ? F : never;

type First = FirstElement<['a', 'b', 'c']>; // 'a'

// Đảo ngược tuple
type Reverse<T extends any[]> = T extends [infer F, ...infer R] 
  ? [...Reverse<R>, F] 
  : [];

type Reversed = Reverse<[1, 2, 3]>; // [3, 2, 1]

7. Phân biệt object, Object và {}

// object - đại diện cho bất kỳ kiểu không phải nguyên thủy
let nonPrimitive: object = { x: 1 };
nonPrimitive = [1, 2, 3];
nonPrimitive = () => {};

// Object - lớp cơ sở của tất cả đối tượng trong JavaScript
let baseObject: Object = new Date();
baseObject = new Map();

// {} - kiểu đối tượng rỗng, tương đương Record<never, never>
let emptyObj: {} = {};
// Có thể gán bất kỳ giá trị không null/undefined
emptyObj = { name: "test" };
emptyObj = [];

8. Covariance và Contravariance

Nguyên tắc phân phối kiểu trong gán giá trị:

// Covariance - Gán từ kiểu rộng sang hẹp (an toàn)
interface Animal { name: string; }
interface Dog extends Animal { breed: string; }

let animal: Animal = { name: "Generic" };
let dog: Dog = { name: "Buddy", breed: "Golden" };

animal = dog; // OK - covariance

// Contravariance - Gán hàm từ tham số hẹp sang rộng
function handleAnimal(a: Animal) { /* ... */ }
function handleDog(d: Dog) { /* ... */ }

// handleDog = handleAnimal; // Error
handleAnimal = handleDog; // OK - contravariance

9. Set, Map và phiên bản Weak

Các cấu trúc dữ liệu ES6 với quản lý bộ nhớ thông minh:

// Set - tập hợp giá trị duy nhất
const uniqueNumbers = new Set([1, 2, 2, 3, 3, 3]);
console.log([...uniqueNumbers]); // [1, 2, 3]

// Map - ánh xạ key-value linh hoạt
const cache = new Map();
cache.set('user', { id: 1, name: 'John' });
cache.set(123, 'some data');

// WeakMap - key là object, tự động dọn khi không còn tham chiếu
const weakCache = new WeakMap();
const objKey = { id: 1 };
weakCache.set(objKey, 'cached data');
// Khi objKey bị gán null, dữ liệu sẽ được GC tự động dọn

Thẻ: typescript decorator proxy mixin utility-types

Đăng vào ngày 16 tháng 6 lúc 16:30