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.ts và 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ệuVai 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 đổiMacro tự chạy khi dữ liệu đổi
transaction(tx => ...)Gom nhiều thay đổi, chỉ thông báo một lầnTắ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 trong derived/autorun thay vì .read(reader) → phụ thuộc không được theo dõi, giao diện không cập nhật. Repo có rule ESLint code-no-observable-get-in-reactive-context để bắt lỗi này.
  • Dùng reader sau await – rule code-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ựcTỷ lệ
editor/browser/widget/diffEditor15 / 24
editor/contrib/inlineCompletions30 / 69
workbench/contrib/chat228 / 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