0
0
Lập trình
NM

Nghệ Thuật Viết Hàm Đẹp Trong TypeScript

Đăng vào 5 ngày trước

• 12 phút đọc

Giới thiệu

TypeScript nâng cao JavaScript với hệ thống kiểu tinh vi, thay đổi cách chúng ta thiết kế và suy nghĩ về các hàm. Khi viết hàm trong TypeScript, chúng ta không chỉ tạo ra mã thực thi mà còn tạo ra các hợp đồng thể hiện ý định, ngăn ngừa lỗi và hướng dẫn phát triển trong tương lai. Các hàm đẹp trong TypeScript là những hàm tận dụng hệ thống kiểu để tạo ra mã vừa an toàn vừa biểu cảm.

Thiết Kế Hàm Dựa Trên Kiểu

Việc thiết kế hàm đẹp trong TypeScript bắt đầu từ các kiểu. Chữ ký kiểu trở thành hợp đồng của hàm, định nghĩa rõ ràng những gì nó nhận vào, những gì nó trả về và những gì có thể xảy ra lỗi.

Ví dụ Hàm Cơ Bản

typescript Copy
function calculateTax(amount: number, rate: number): number {
    return amount * (rate / 100);
}

Hàm với Tham Số Đối Tượng

typescript Copy
interface TaxCalculation {
    readonly grossAmount: number;
    readonly taxRate: number;
    readonly region: 'US' | 'EU' | 'BD';
}

function calculateRegionalTax(params: TaxCalculation): number {
    const { grossAmount, taxRate, region } = params;
    const baseAmount = grossAmount * (taxRate / 100);

    const regionalMultiplier = {
        US: 1.0,
        EU: 1.2,
        BD: 1.5  
    }[region];

    return baseAmount * regionalMultiplier;
}

Mã trên tính thuế dựa trên cả tỷ lệ thuế và khu vực, đảm bảo rằng các địa điểm khác nhau tuân thủ các quy tắc riêng của họ. Điều này giúp giữ cho logic đơn giản, có thể tái sử dụng và dễ mở rộng nếu có thêm khu vực mới.

Hàm Generic: Linh Hoạt và An Toàn

Hàm generic đại diện cho khả năng tạo ra các trừu tượng linh hoạt nhưng vẫn an toàn về kiểu trong TypeScript. Chúng cho phép viết các hàm hoạt động với nhiều kiểu khác nhau trong khi vẫn duy trì đầy đủ an toàn kiểu.

Ví dụ Hàm Generic Đơn Giản

typescript Copy
function identity<T>(value: T): T {
    return value;
}

Ví dụ Thực Tế với Ràng Buộc

typescript Copy
interface Identifiable {
    id: string;
}

function findById<T extends Identifiable>(
    items: readonly T[], 
    id: string
): T | undefined {
    return items.find(item => item.id === id);
}

Hàm Generic Nâng Cao với Nhiều Tham Số Kiểu

typescript Copy
function mapWithIndex<T, U>(
    array: readonly T[],
    mapper: (item: T, index: number) => U
): U[] {
    return array.map(mapper);
}

Ví dụ Sử Dụng

typescript Copy
const users = [
    { id: '1', name: 'Alice', age: 30 },
    { id: '2', name: 'Bob', age: 25 }
];

const userNames = mapWithIndex(users, (user, index) => `${index}: ${user.name}`);
// userNames được suy diễn là string[]

Nạp Chồng Hàm: Hợp Đồng Kiểu Chính Xác

Nạp chồng hàm trong TypeScript cho phép chúng ta định nghĩa nhiều chữ ký kiểu cho một hàm, cung cấp thông tin kiểu chính xác cho các mẫu sử dụng khác nhau.

Ví dụ Nạp Chồng Hàm

typescript Copy
function processData(data: string): string;
function processData(data: number): number;
function processData(data: string[]): string[];
function processData(data: string | number | string[]): string | number | string[] {
    if (typeof data === 'string') {
        return data.toUpperCase();
    }

    if (typeof data === 'number') {
        return data * 2;
    }

    return data.map(item => item.toUpperCase());
}

const stringResult = processData("hello");     // string
const numberResult = processData(42);          // number
const arrayResult = processData(["a", "b"]);  // string[]

Xử Lý Lỗi với Kiểu Kết Quả

Các hàm đẹp trong TypeScript thực hiện việc xử lý lỗi một cách rõ ràng và an toàn kiểu bằng cách sử dụng các kiểu kết quả hoặc các mẫu tương tự.

Định Nghĩa Kiểu Kết Quả

typescript Copy
type Result<T, E = Error> = {
    success: true;
    data: T;
} | {
    success: false;
    error: E;
};

Các Kiểu Lỗi Tùy Chỉnh

typescript Copy
class ValidationError extends Error {
    constructor(public field: string, message: string) {
        super(message);
        this.name = 'ValidationError';
    }
}

class NetworkError extends Error {
    constructor(public statusCode: number, message: string) {
        super(message);
        this.name = 'NetworkError';
    }
}

Hàm Sử Dụng Kiểu Kết Quả

typescript Copy
async function fetchUserProfile(userId: string): Promise<Result<UserProfile, ValidationError | NetworkError>> {
    if (!userId.trim()) {
        return {
            success: false,
            error: new ValidationError('userId', 'User ID không được để trống')
        };
    }

    try {
        const response = await fetch(`/api/users/${userId}`);

        if (!response.ok) {
            return {
                success: false,
                error: new NetworkError(response.status, 'Không thể lấy thông tin người dùng')
            };
        }

        const profile = await response.json() as UserProfile;

        return {
            success: true,
            data: profile
        };
    } catch (error) {
        return {
            success: false,
            error: new NetworkError(0, 'Yêu cầu mạng thất bại')
        };
    }
}

Sử Dụng với Xử Lý Lỗi Rõ Ràng

typescript Copy
async function displayUserProfile(userId: string): Promise<void> {
    const result = await fetchUserProfile(userId);

    if (result.success) {
        console.log(`Người dùng: ${result.data.name}`);
    } else {
        if (result.error instanceof ValidationError) {
            console.error(`Lỗi xác thực ở ${result.error.field}: ${result.error.message}`);
        } else if (result.error instanceof NetworkError) {
            console.error(`Lỗi mạng (${result.error.statusCode}): ${result.error.message}`);
        }
    }
}

Hàm Bậc Cao và Thành Phần Hàm

TypeScript nổi bật trong việc gán kiểu cho các hàm bậc cao và các mẫu thành phần hàm.

Ví dụ Thành Phần Hàm An Toàn Kiểu

typescript Copy
function compose<A, B, C>(
    f: (b: B) => C,
    g: (a: A) => B
): (a: A) => C {
    return (a: A) => f(g(a));
}

Ví dụ Thực Tế với Các Hoạt Động Pipeline

typescript Copy
interface User {
    id: string;
    name: string;
    email: string;
    age: number;
}

const validateEmail = (email: string): boolean => 
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

const normalizeEmail = (email: string): string => 
    email.toLowerCase().trim();

const createEmailValidator = compose(validateEmail, normalizeEmail);

Xử Lý Mảng với Kiểu Mạnh

typescript Copy
function processUsers<T extends User>(
    users: readonly T[],
    processors: Array<(user: T) => T>
): T[] {
    return users.map(user => 
        processors.reduce((acc, processor) => processor(acc), user)
    );
}

Ví dụ Sử Dụng

typescript Copy
const addFullName = (user: User): User => ({
    ...user,
    fullName: `${user.name} <${user.email}>`
});

const incrementAge = (user: User): User => ({
    ...user,
    age: user.age + 1
});

const processedUsers = processUsers(users, [addFullName, incrementAge]);

Các Kiểu Tiện Ích và Mẫu Nâng Cao

Các kiểu tiện ích của TypeScript cho phép thiết kế hàm tinh vi mà vẫn giữ an toàn kiểu trong khi cung cấp tính linh hoạt.

Sử Dụng Kiểu Tiện Ích cho Tham Số Hàm

typescript Copy
function updateUser<K extends keyof User>(
    user: User,
    updates: Pick<User, K>
): User {
    return { ...user, ...updates };
}

Kiểu Điều Kiện cho APIs Linh Hoạt

typescript Copy
type ApiResponse<T> = T extends string 
    ? { message: T } 
    : { data: T };

function createResponse<T>(payload: T): ApiResponse<T> {
    if (typeof payload === 'string') {
        return { message: payload } as ApiResponse<T>;
    }
    return { data: payload } as ApiResponse<T>;
}

Các Kiểu Chuỗi Mẫu cho Các Hoạt Động Chuỗi An Toàn Kiểu

typescript Copy
type EventName<T extends string> = `on${Capitalize<T>}`;

function createEventHandler<T extends string>(
    eventName: T,
    handler: (event: any) => void
): Record<EventName<T>, (event: any) => void> {
    const handlerName = `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}` as EventName<T>;
    return { [handlerName]: handler } as Record<EventName<T>, (event: any) => void>;
}

const clickHandler = createEventHandler('click', (e) => console.log('Đã nhấp!'));

Thiết Kế Hàm Asynchronous

Các hàm async đẹp trong TypeScript xử lý các promise và lỗi một cách nhẹ nhàng trong khi duy trì các hợp đồng kiểu rõ ràng.

Các Hoạt Động An Toàn Kiểu với Xử Lý Lỗi Đúng Cách

typescript Copy
interface ApiConfig {
    baseUrl: string;
    timeout: number;
    retries: number;
}

async function withRetry<T>(
    operation: () => Promise<T>,
    maxRetries: number,
    delay: number = 1000
): Promise<T> {
    let lastError: Error;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await operation();
        } catch (error) {
            lastError = error instanceof Error ? error : new Error(String(error));

            if (attempt === maxRetries) {
                break;
            }

            await new Promise(resolve => setTimeout(resolve, delay * attempt));
        }
    }

    throw lastError!;
}

Hàm Async với Xử Lý Lỗi Toàn Diện

typescript Copy
async function fetchWithConfig<T>(
    endpoint: string,
    config: ApiConfig
): Promise<Result<T, NetworkError | ValidationError>> {
    try {
        const operation = async (): Promise<T> => {
            const controller = new AbortController();
            const timeoutId = setTimeout(() => controller.abort(), config.timeout);

            try {
                const response = await fetch(`${config.baseUrl}${endpoint}`, {
                    signal: controller.signal
                });

                if (!response.ok) {
                    throw new NetworkError(response.status, `HTTP ${response.status}`);
                }

                return await response.json();
            } finally {
                clearTimeout(timeoutId);
            }
        };

        const data = await withRetry(operation, config.retries);

        return { success: true, data };
    } catch (error) {
        if (error instanceof NetworkError) {
            return { success: false, error };
        }

        return {
            success: false,
            error: new NetworkError(0, error instanceof Error ? error.message : 'Lỗi không xác định')
        };
    }
}

Tại Sao Thiết Kế Hàm TypeScript Quan Trọng

Hệ thống kiểu của TypeScript chuyển đổi các hàm từ mã thực thi đơn giản thành các hợp đồng tự tài liệu. Khi chúng ta thiết kế các hàm với hệ thống kiểu của TypeScript, chúng ta tạo ra mã không chỉ mạnh mẽ hơn mà còn biểu cảm hơn và dễ bảo trì hơn.

Chữ ký kiểu phục vụ như tài liệu sống không bao giờ bị mất đồng bộ với việc thực hiện. Chúng phát hiện lỗi ở thời gian biên dịch thay vì thời gian chạy, giảm thiểu lỗi và cải thiện sự tự tin của lập trình viên. Chúng kích hoạt các tính năng mạnh mẽ của IDE như tự động hoàn thành, tái cấu trúc và điều hướng.

Quan trọng nhất, các hàm có kiểu tốt giúp mã dễ đọc và dễ hiểu hơn. Một lập trình viên có thể nhìn vào chữ ký hàm và ngay lập tức hiểu những gì hàm mong đợi, những gì nó trả về và những gì có thể xảy ra lỗi mà không cần đọc việc thực hiện.

Các hàm đẹp trong TypeScript đại diện cho sự kết hợp giữa nguyên tắc thiết kế hàm với kiểu tĩnh mạnh. Chúng chứng minh rằng an toàn kiểu và khả năng biểu cảm không phải là các lực đối kháng mà là các khía cạnh bổ sung của thiết kế phần mềm tuyệt vời. Khi viết các hàm TypeScript tận dụng tối đa hệ thống kiểu, chúng ta tạo ra mã vừa an toàn vừa thanh lịch - dấu hiệu của phần mềm đẹp.

Kết Luận

Hành trình từ việc viết các hàm JavaScript đơn giản đến việc tạo ra các hàm TypeScript đẹp là một quá trình biến đổi. Không chỉ đơn thuần là việc thêm kiểu vào mã hiện có - mà còn là việc thay đổi cách chúng ta suy nghĩ về thiết kế hàm, xử lý lỗi và các hợp đồng API.

Các hàm TypeScript đẹp là những khoản đầu tư cho tương lai. Chúng giảm thiểu thời gian gỡ lỗi, làm cho việc tái cấu trúc an toàn hơn, kích hoạt công cụ tốt hơn và tạo ra các mã tự tài liệu. Khi một lập trình viên mới gia nhập nhóm của bạn, các hàm có kiểu tốt truyền đạt ý định của chúng ngay lập tức, giảm thiểu thời gian học hỏi và ngăn chặn những hiểu lầm.

Cuối cùng, nghệ thuật của các hàm TypeScript đẹp nằm ở sự cân bằng giữa an toàn và linh hoạt, giữa các hợp đồng rõ ràng và API dễ sử dụng. Khi bạn đạt được sự cân bằng này, các hàm của bạn trở thành nhiều hơn chỉ là mã - chúng trở thành các khối xây dựng đáng tin cậy cho các hệ thống lớn hơn, phức tạp hơn trong khi vẫn dễ hiểu và bảo trì.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào