1. Chào mừng đến với RxJS
RxJS là gì?
RxJS (Reactive Extensions for JavaScript) là một thư viện giúp lập trình với các luồng dữ liệu bất đồng bộ. Hãy tưởng tượng bạn đang xử lý mọi thứ, từ sự kiện click chuột, nhập liệu, đến dữ liệu từ API, như những dòng sông (streams) chảy liên tục. RxJS cung cấp cho bạn các công cụ mạnh mẽ để điều khiển, biến đổi và kết hợp những dòng sông này.
💡 Phép so sánh: Dây chuyền sản xuất
Hãy xem một Observable như một dây chuyền sản xuất. Nó đưa ra các "sản phẩm thô" (dữ liệu). Các Operators (như map
, filter
) là các cỗ máy trên dây chuyền, mỗi máy thực hiện một công đoạn xử lý. Cuối cùng, Observer (người đăng ký) là người nhận "thành phẩm" (dữ liệu đã qua xử lý).
Observable đầu tiên của bạn
Đây là một Observable đơn giản nhất, sử dụng hàm of
để tạo ra một luồng dữ liệu phát ra một vài giá trị rồi kết thúc.
2. Các Operators Cơ Bản
map(fn)
- Biến đổi
Biến đổi mỗi giá trị được phát ra bởi luồng nguồn. Ví dụ: nhân mỗi số với 10.
filter(fn)
- Lọc
Chỉ cho phép các giá trị thỏa mãn một điều kiện đi qua. Ví dụ: chỉ lấy các số chẵn.
tap(fn)
- Hành động phụ
Thực hiện một hành động phụ (ví dụ: logging) với mỗi giá trị mà không làm thay đổi luồng.
scan(fn, seed)
- Tích lũy
Tích lũy giá trị theo thời gian, tương tự reduce
của Array nhưng phát ra mỗi giá trị trung gian.
3. Operators Chuyển Đổi (Flattening)
Các operators này xử lý "Observable của Observable" (luồng lồng trong luồng). Tình huống phổ biến là khi một sự kiện (như click) kích hoạt một tác vụ bất đồng bộ khác (như gọi API).
mergeMap
- Hợp nhất luồng
Tạo và đăng ký vào một luồng con cho mỗi giá trị từ luồng cha. Tất cả các luồng con chạy song song và kết quả được hợp nhất.
switchMap
- Chuyển luồng
Khi có giá trị mới từ nguồn, `switchMap` sẽ hủy (unsubscribe) luồng con (inner observable) trước đó và chuyển sang luồng con mới. Rất hữu ích để tránh các tác vụ lỗi thời.
Giống mergeMap
, nhưng khi luồng cha phát giá trị mới, nó sẽ hủy luồng con trước đó và chuyển sang luồng con mới.
concatMap
- Nối luồng
Chạy các luồng con một cách tuần tự. Luồng con tiếp theo chỉ bắt đầu khi luồng con trước đó đã hoàn thành. Đảm bảo thứ tự.
exhaustMap
- Bỏ qua luồng
Bỏ qua các giá trị mới từ luồng cha trong khi một luồng con đang hoạt động. Hữu ích để chống click-spam.
4. Operators Kết Hợp (Combination)
Các operators này cho phép bạn kết hợp nhiều luồng dữ liệu lại với nhau theo những cách khác nhau.
combineLatest
- Kết hợp giá trị mới nhất
Khi bất kỳ luồng nào phát, nó sẽ phát ra một mảng chứa giá trị mới nhất từ TẤT CẢ các luồng. Sẽ không phát gì cho đến khi mọi luồng đã phát ít nhất một lần.
forkJoin
- Chờ tất cả hoàn thành
Tương tự Promise.all
. Chờ cho tất cả các luồng nguồn hoàn thành, sau đó phát ra một mảng chứa giá trị CUỐI CÙNG từ mỗi luồng.
5. Utility Operators
Đây là tập hợp các toán tử tiện ích mạnh mẽ để điều khiển luồng dữ liệu, đặc biệt hữu ích trong việc xử lý tương tác người dùng hoặc các nguồn phát dữ liệu dày đặc.
So sánh debounceTime
và throttleTime
Click vào nút bên dưới thật nhanh và nhiều lần để xem sự khác biệt. Debounce chỉ phát giá trị sau khi có một khoảng lặng. Throttle phát giá trị đầu tiên, sau đó chặn trong một khoảng thời gian.
bufferTime(2000)
Gom các giá trị từ luồng nguồn vào một bộ đệm và phát ra bộ đệm này sau mỗi 2 giây.
pairwise()
Nhóm các giá trị liên tiếp thành từng cặp `[trước, sau]` và phát ra.
withLatestFrom(other$)
Khi luồng chính (Clicks) phát, nó sẽ kết hợp giá trị của mình với giá trị mới nhất từ luồng phụ (Timer). Luồng phụ chạy ngầm.
6. Operators Hữu Ích Khác
take(count)
- Giới Hạn Số Lượng
Chỉ lấy một số lượng giá trị xác định từ luồng dữ liệu.
zip(obs1,...)
- Ghép Cặp
Ghép cặp các giá trị từ nhiều luồng theo thứ tự xuất hiện.
catchError(fn)
- Xử Lý Lỗi
Bắt và xử lý lỗi trong luồng dữ liệu, trả về một giá trị thay thế hoặc luồng mới.
startWith(...values)
- Bắt Đầu Với Giá Trị
Thêm các giá trị vào đầu luồng dữ liệu trước khi luồng chính bắt đầu phát giá trị.
7. Subjects - Đa Hướng Dữ Liệu
Subject là một dạng đặc biệt của Observable cho phép đa hướng dữ liệu - nhiều Observer có thể subscribe vào cùng một Subject. Subject cũng là một Observer, có thể nhận dữ liệu thông qua các phương thức next(), error() và complete().
Subject
Subject cơ bản - Observer chỉ nhận các giá trị phát ra sau khi subscribe.
BehaviorSubject
Lưu trữ giá trị hiện tại và phát nó cho các subscriber mới ngay khi họ subscribe.
ReplaySubject
Lưu trữ một số lượng giá trị đã phát trước đó và phát lại cho các subscriber mới.
8. Ví Dụ Thực Tế
8.1. Gợi ý tìm kiếm (Typeahead)
Một ứng dụng kinh điển của RxJS: đợi người dùng ngừng gõ, sau đó mới thực hiện tìm kiếm để tránh các request không cần thiết.
8.2. Thanh tiến trình (Progress Bar)
Sử dụng RxJS để điều khiển một thanh tiến trình, rất hữu ích cho việc hiển thị trạng thái tải file hoặc các tác vụ kéo dài.
8.3. Xác thực biểu mẫu (Real-time Form Validation)
Sử dụng combineLatest
để tổng hợp trạng thái hợp lệ của nhiều trường và chỉ cho phép gửi khi tất cả đều OK. Tên người dùng "admin" hoặc "root" sẽ bị báo là đã tồn tại (kiểm tra bất đồng bộ).
9. Ví Dụ Khó Nhằn
9.1. Tìm Kiếm Nâng Cao: Loading & Xử Lý Lỗi
Tìm kiếm thông minh với hiệu ứng loading, xử lý lỗi và gợi ý tự động.
9.2. Kéo Thả Bằng RxJS
Triển khai tính năng kéo thả sử dụng các Observable để xử lý sự kiện.
9.3. Lấy Dữ Liệu Định Kỳ (Polling)
Tự động lấy dữ liệu mới từ server theo chu kỳ với khả năng dừng lại.
10. Ví Dụ Siêu Khó Nhằn
10.1. Gợi Ý Từ Nhiều Nguồn API
Gõ vào ô tìm kiếm, hệ thống sẽ gọi đồng thời nhiều API khác nhau và kết hợp kết quả.
10.2. Chuỗi Tác Vụ Tuần Tự & Thử Lại
Thực hiện một chuỗi tác vụ tuần tự, mỗi tác vụ có thể thử lại nếu gặp lỗi.
10.3. Cuộn Vô Tận (Infinite Scroll)
Tự động tải thêm dữ liệu khi người dùng cuộn đến cuối trang.
11. Ứng Dụng Nâng Cao
11.1. Tự Động Lưu Form
Tự động lưu nội dung khi người dùng ngừng gõ, hiển thị trạng thái lưu.
11.2. WebSocket với Tự Động Kết Nối Lại
Kết nối đến một server WebSocket, gửi tin nhắn, và tự động kết nối lại khi mất kết nối.