Observables
← Về MOC · Trước: Disposable & Event
1. Khái niệm
Observable là gì?
Observable (giá trị quan sát được) là một giá trị mà khi thay đổi, mọi thứ phụ thuộc vào nó tự động được tính lại. Đây là phong cách reactive programming (lập trình phản ứng), giống ô Excel có công thức
=A1*B1: sửa A1 thì ô công thức tự cập nhật.
VS Code có thư viện observable riêng (comment trong hướng dẫn chính thức so sánh autorun với autorun của thư viện MobX):
- Mã nguồn:
src/vs/base/common/observable.tsvàsrc/vs/base/common/observableInternal/ - Hướng dẫn:
.github/instructions/observables.instructions.md
So với Event: với Event, bạn phải tự đăng ký/hủy listener và tự tính lại giá trị dẫn xuất. Với observable, phụ thuộc được theo dõi tự động.
2. Các ký hiệu chính
| Ký hiệu | Vai trò | Tương tự Excel |
|---|---|---|
observableValue(owner, init) | Giá trị gốc, đọc/ghi được | Ô nhập số |
derived(owner, reader => ...) | Giá trị dẫn xuất, tự tính lại | Ô công thức |
autorun(reader => ...) | Hiệu ứng phụ (side effect) chạy lại khi phụ thuộc đổi | Macro tự chạy khi dữ liệu đổi |
transaction(tx => ...) | Gom nhiều thay đổi, chỉ thông báo một lần | Tắt tự tính toán, sửa nhiều ô, rồi bật lại |
observableFromEvent(...) | Chuyển một Event cũ thành observable | — |
3. Ví dụ chính thức (trích hướng dẫn, dịch chú thích)
class MyService extends Disposable {
// Giá trị gốc: luôn truyền `this` làm owner, rồi giá trị khởi tạo
private _myData1 = observableValue(this, 0);
private _myData2 = observableValue(this, 42);
// Giá trị dẫn xuất: đọc qua .read(reader) để ĐƯỢC THEO DÕI phụ thuộc
private _myDerivedData = derived(this, reader => {
return this._myData1.read(reader) * this._myData2.read(reader);
});
constructor() {
super();
// autorun chạy ngay lần đầu và chạy lại mỗi khi _myData1 thay đổi
this._register(autorun(reader => {
const data = this._myData1.read(reader);
console.log(data);
}));
}
getData(): number {
// .get() đọc không theo dõi phụ thuộc – hạn chế dùng trong ngữ cảnh reactive
return this._myData1.get();
}
setData2() {
// Cập nhật nhiều giá trị cùng lúc → dùng transaction để chỉ thông báo 1 lần
transaction(tx => {
this._myData1.set(42, tx);
this._myData2.set(43, tx);
});
}
}Lỗi thường gặp
- Gọi
.get()bên trongderived/autorunthay vì.read(reader)→ phụ thuộc không được theo dõi, giao diện không cập nhật. Repo có rule ESLintcode-no-observable-get-in-reactive-contextđể bắt lỗi này.- Dùng
readersauawait– rulecode-no-reader-after-await.- Quên
this._register(autorun(...))→ autorun không bao giờ dừng (leak).
4. Ví dụ minh họa gần nghiệp vụ (tự viết)
// Tổng tiền hóa đơn tự cập nhật khi số lượng hoặc đơn giá thay đổi
class InvoiceLineModel extends Disposable {
readonly quantity = observableValue(this, 1);
readonly unitPrice = observableValue(this, 0);
readonly vatRate = observableValue(this, 0.1); // thuế GTGT 10%
// Thành tiền trước thuế
readonly amount = derived(this, r => this.quantity.read(r) * this.unitPrice.read(r));
// Tiền thuế và tổng thanh toán – tự tính lại theo chuỗi phụ thuộc
readonly vatAmount = derived(this, r => this.amount.read(r) * this.vatRate.read(r));
readonly total = derived(this, r => this.amount.read(r) + this.vatAmount.read(r));
constructor(label: HTMLElement) {
super();
// Cập nhật giao diện mỗi khi total đổi
this._register(autorun(r => {
label.textContent = this.total.read(r).toLocaleString('vi-VN');
}));
}
updateLine(qty: number, price: number): void {
transaction(tx => { // 2 thay đổi → giao diện chỉ vẽ lại 1 lần
this.quantity.set(qty, tx);
this.unitPrice.set(price, tx);
});
}
}5. Khi nào dùng?
Số liệu đếm thô tại commit khảo sát (số file .ts có dùng observableValue/derived( trên tổng số file):
| Khu vực | Tỷ lệ |
|---|---|
editor/browser/widget/diffEditor | 15 / 24 |
editor/contrib/inlineCompletions | 30 / 69 |
workbench/contrib/chat | 228 / 1.416 |
workbench/contrib/files (code lâu đời) | 0 / 40 |
Nhận xét: khu vực mới phát triển dùng observable nhiều; khu vực lâu đời vẫn dùng Emitter/Event. Khi sửa code, theo phong cách sẵn có của file đó.
Liên kết
- Tiếp theo (phần extension): Extension Host & RPC