🗓️ Tuần 3: Type nâng cao – Lập trình mạnh mẽ hơn
Tuần này chúng ta sẽ khám phá sức mạnh thực sự của TypeScript system - từ Generic đến Conditional Types. Đây là những tính năng giúp TS vượt trội so với Python typing.
19. ⚙️ Generic Type là gì?
Python Generic (từ 3.5+)
from typing import TypeVar, Generic, List
T = TypeVar('T')
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: List[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
TypeScript Generic (mạnh mẽ hơn)
// Function generic
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42); // T = number
const str = identity("hello"); // T được infer = string
// Class generic
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
}
const numberStack = new Stack<number>();
const stringStack = new Stack<string>();
// Interface generic
interface ApiResponse<TData> {
data: TData;
status: number;
error?: string;
}
const userResponse: ApiResponse<User[]> = {
data: [{ name: "Alice", age: 30 }],
status: 200
};
💡 Takeaway: TS Generic linh hoạt và mạnh mẽ hơn Python, có type inference tốt hơn.
20. 📐 Utility types phổ biến
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Partial<T> - tất cả properties optional
type UserUpdate = Partial<User>;
const update: UserUpdate = { name: "New Name" }; // OK
// Pick<T, K> - chọn một số properties
type UserProfile = Pick<User, 'id' | 'name' | 'email'>;
const profile: UserProfile = {
id: 1,
name: "Alice",
email: "alice@example.com"
// password không cần
};
// Omit<T, K> - loại bỏ một số properties
type CreateUserRequest = Omit<User, 'id' | 'createdAt'>;
const createRequest: CreateUserRequest = {
name: "Bob",
email: "bob@example.com",
password: "secret123"
};
// Record<K, V> - tạo object type với key/value specific
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
const roles: UserRoles = {
"alice": "admin",
"bob": "user",
"charlie": "guest"
};
// Required<T> - tất cả properties required
type RequiredUser = Required<Partial<User>>;
// Readonly<T> - tất cả properties readonly
type ImmutableUser = Readonly<User>;
Python tương đương (hạn chế hơn):
from typing import TypedDict, Optional
class UserUpdate(TypedDict, total=False): # Similar to Partial
name: Optional[str]
email: Optional[str]
21. 🔎 Type inference & narrowing
// Type narrowing với typeof
function processValue(value: string | number) {
if (typeof value === "string") {
// TS biết value là string trong block này
return value.toUpperCase();
}
// TS biết value là number ở đây
return value.toFixed(2);
}
// Type narrowing với instanceof
class Dog {
bark(): void { console.log("Woof!"); }
}
class Cat {
meow(): void { console.log("Meow!"); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // TS biết là Dog
} else {
animal.meow(); // TS biết là Cat
}
}
// Type narrowing với in operator
interface Bird {
fly(): void;
feathers: boolean;
}
interface Fish {
swim(): void;
scales: boolean;
}
function move(animal: Bird | Fish) {
if ('fly' in animal) {
animal.fly(); // TS biết là Bird
} else {
animal.swim(); // TS biết là Fish
}
}
// Type guards (custom)
function isString(value: unknown): value is string {
return typeof value === "string";
}
function processUnknown(value: unknown) {
if (isString(value)) {
// TS biết value là string
console.log(value.length);
}
}
So với Python: TS type narrowing mạnh mẽ hơn và tự động hơn.
22. 🔁 Mapped types
// Tạo type mới dựa trên type cũ
type Optional<T> = {
[K in keyof T]?: T[K];
};
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// Ví dụ sử dụng
interface User {
name: string;
age: number;
email: string;
}
type OptionalUser = Optional<User>;
// = { name?: string; age?: number; email?: string; }
type NullableUser = Nullable<User>;
// = { name: string | null; age: number | null; email: string | null; }
// Advanced mapped type
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// = {
// getName: () => string;
// getAge: () => number;
// getEmail: () => string;
// }
// Conditional mapping
type NonFunctionKeys<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type DataOnly<T> = Pick<T, NonFunctionKeys<T>>;
💡 Takeaway: Mapped types cho phép tạo types mới từ types cũ một cách tự động.
23. ❓ Conditional types
// Conditional type cơ bản
type IsArray<T> = T extends any[] ? true : false;
type Test1 = IsArray<string[]>; // true
type Test2 = IsArray<number>; // false
// Với generic
type ArrayElement<T> = T extends (infer U)[] ? U : T;
type StringElement = ArrayElement<string[]>; // string
type NumberElement = ArrayElement<number>; // number
// Practical example: API response wrapper
type ApiResult<T> = T extends string
? { message: T }
: T extends number
? { count: T }
: { data: T };
type MessageResult = ApiResult<string>;
// = { message: string }
type CountResult = ApiResult<number>;
// = { count: number }
type DataResult = ApiResult<User[]>;
// = { data: User[] }
// Distribute over union
type ToArray<T> = T extends any ? T[] : never;
type Test = ToArray<string | number>; // string[] | number[]
// Exclude/Extract utilities
type WithoutNull<T> = T extends null ? never : T;
type NonNull = WithoutNull<string | null | number>; // string | number
24. 🧬 Structural typing là gì?
// TypeScript sử dụng structural typing (duck typing)
interface Point {
x: number;
y: number;
}
interface Vector {
x: number;
y: number;
}
function distance(p1: Point, p2: Point): number {
return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
}
// Vector có cùng structure với Point
const vector: Vector = { x: 1, y: 2 };
const point: Point = { x: 3, y: 4 };
console.log(distance(vector, point)); // OK! Structural typing
// Object literal có thêm properties
const extendedPoint = { x: 1, y: 2, z: 3, color: "red" };
console.log(distance(extendedPoint, point)); // OK! Extra properties ignored
// Nhưng cẩn thận với excess property checks
function createPoint(config: Point): Point {
return config;
}
// Error: excess property 'z'
// createPoint({ x: 1, y: 2, z: 3 });
// OK: assign to variable first
const config = { x: 1, y: 2, z: 3 };
createPoint(config); // OK
So với Python: Giống duck typing nhưng được kiểm tra tại compile time.
25. 🧠 So sánh với Python TypedDict
Python TypedDict
from typing import TypedDict, Optional
class User(TypedDict):
name: str
age: int
email: Optional[str]
# Usage
user: User = {"name": "Alice", "age": 30} # email optional
TypeScript Interface (mạnh mẽ hơn)
interface User {
name: string;
age: number;
email?: string;
}
// Advanced features không có trong Python
interface UserMethods {
getName(): string;
setAge(age: number): void;
}
// Inheritance
interface AdminUser extends User {
permissions: string[];
}
// Index signatures
interface FlexibleUser {
name: string;
[key: string]: any; // Allow additional properties
}
// Computed property names
type DynamicUser = {
[K in `user_${string}`]: string;
};
💡 Takeaway: TS interfaces linh hoạt hơn Python TypedDict rất nhiều.
26. 🧪 Refactor JS cũ sang TS
JavaScript code cũ
// Legacy JavaScript
function processUsers(users) {
return users
.filter(user => user.active)
.map(user => ({
id: user.id,
displayName: user.firstName + ' ' + user.lastName,
email: user.email.toLowerCase()
}))
.sort((a, b) => a.displayName.localeCompare(b.displayName));
}
const apiCall = async (url, options) => {
const response = await fetch(url, options);
const data = await response.json();
return data;
};
Refactored TypeScript
// TypeScript with proper typing
interface RawUser {
id: number;
firstName: string;
lastName: string;
email: string;
active: boolean;
}
interface ProcessedUser {
id: number;
displayName: string;
email: string;
}
function processUsers(users: RawUser[]): ProcessedUser[] {
return users
.filter((user): user is RawUser & { active: true } => user.active)
.map(user => ({
id: user.id,
displayName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase()
}))
.sort((a, b) => a.displayName.localeCompare(b.displayName));
}
// Generic API call
async function apiCall<T = unknown>(
url: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data as T;
}
// Usage with type safety
const users = await apiCall<RawUser[]>('/api/users');
const processed = processUsers(users);
27. 🧪 Bài tập: Viết groupBy() có kiểu
// Challenge: Implement typed groupBy function
function groupBy<T, K extends keyof T>(
array: T[],
key: K
): Record<string, T[]> {
return array.reduce((groups, item) => {
const groupKey = String(item[key]);
if (!groups[groupKey]) {
groups[groupKey] = [];
}
groups[groupKey].push(item);
return groups;
}, {} as Record<string, T[]>);
}
// More advanced version with custom key function
function groupByFn<T, K extends string | number>(
array: T[],
keyFn: (item: T) => K
): Record<K, T[]> {
return array.reduce((groups, item) => {
const key = keyFn(item);
if (!groups[key]) {
groups[key] = [] as T[];
}
groups[key].push(item);
return groups;
}, {} as Record<K, T[]>);
}
// Usage examples
interface Person {
name: string;
age: number;
department: string;
}
const people: Person[] = [
{ name: "Alice", age: 30, department: "Engineering" },
{ name: "Bob", age: 25, department: "Marketing" },
{ name: "Charlie", age: 35, department: "Engineering" }
];
// Group by property
const byDepartment = groupBy(people, 'department');
// Type: Record<string, Person[]>
// Group by custom function
const byAgeGroup = groupByFn(people, person =>
person.age < 30 ? 'young' : 'experienced'
);
// Type: Record<'young' | 'experienced', Person[]>
🎯 Bài tập tuần 3
Bài 1: Utility Type Creator
Tạo utility type DeepPartial<T>
làm tất cả properties (kể cả nested) thành optional.
Bài 2: API Response Wrapper
Viết generic type ApiResponse<T>
và function handleApiResponse<T>()
với error handling.
Bài 3: Form Validation
Tạo type system cho form validation với conditional types.
Bài 4: Database Model
Thiết kế typed database model với relationships sử dụng mapped types.
🚀 Tuần tới
👉 Tuần 4: Ứng dụng TypeScript trong Frontend / AI
Tuần cuối chúng ta sẽ áp dụng tất cả kiến thức đã học vào React và AI applications!
🎉 Chúc mừng bạn đã hoàn thành tuần 3 - phần khó nhất!