- Một ví dụ về Reactivity của Vue
- Vấn đề và giải pháp
- Giải pháp tổng quát hơn
- Tách Dep cho mỗi biến
- Tổng hợp các ý tưởng chính
Một ví dụ về Reactivity của Vue
<div id='app'>
<div>Price: ${{ price }}</div>
<div>Total: ${{ price * quantity }}</div>
<div>Taxes: ${{ totalPriceWithTax }}</div>
</div>var vm = new Vue({
el: '#app',
data: {
price: 5.00,
quantity: 2
},
computed: {
totalPriceWithTax() {
return this.price * this.quantity * 1.03
}
}
})Ở đây khi chúng ta thay đổi giá trị của price, thằng Vue nó sẽ làm 3 thứ
- Cập nhập lại giá trị
price - Tính lại giá trị
total - Gọi lại hàm
totalPriceWithTaxvà cập nhập lại giá trị

Thấy hết sức bình thường, nhưng đó KHÔNG PHẢI LÀ CÁCH CHẠY BÌNH THƯỜNG CỦA JAVASCRIPT
Ví dụ với javascript bình thường
let price = 5
let quantity = 2
let total = price * quantity // kết quả sẽ là 10
price = 20 // gán lại giá trị của price
console.log(`total is ${total}`)Bạn hãy đoán xem kết quả log ra là mấy? Sẽ là 10 chứ không phải 40 đâu.
Vấn đề và giải pháp
Vấn đề là chúng ta cần phải lưu cái cách tính price * quantity này lại ở đâu đó, để chúng ta re-run cách tính này khi gọi lại total, nó sẽ không nên là biến số mà là thành hàm, thì khi đó nếu giá trị price hoặc quantity thay đổi chúng ta sẽ có kết quả total thay đổi theo.
Chúng ta cần một nơi để lưu phần code tính toán kiểu như vậy lại ở đâu đó, để khi price hoặc quantity thay đổi, chúng ta sẽ chạy lại tất cả những gì đã lưu

let price = 5;
let quantity = 2;
let total = 0;
let target = () => { total = price * quantity }
record(); // lưu lại đâu đó để re-run sau này
target(); // Hàm record chúng ta sẽ implement nó như sau
let storage = []; // đưa toàn bộ các hàm muốn re-run vào mảng này
function record() {
storage.push(target);
}
// hàm để chạy lại tất cả những thứ đã lưu trong store
function replay() {
storage.forEach(run => run());
}Giải pháp tổng quát hơn
Nếu đã nắm được ý tưởng chính để giải quyết bài toán ban đầu, giờ chúng ta sẽ hiện thực hóa nó bẳng observer pattern, tạo một class để quản lý những chuyện đó
class Dep {
constructor() {
// thay vì là starage, thiên hạ đã thống nhất lấy cái tên subscribers
this.subscribers = [];
}
depend() {
if (target && !this.subscribers.includes(target)) {
// chỉ thêm vào nếu chưa có hoặc không trùng
thiss.subscribers.push(target);
}
}
notify() {
// run tất cả target, tên gọi khác là observer
this.subscribers.forEach(sub => sub());
}
}Code lại ví dụ trên sử dụng class mới tạo này
const dep = new Dep();
let price = 5;
let quantity = 2;
let total = 0;
let target = () => { total = price * quantity }
dep.depend();
target();
console.log(total); // 10
price = 20;
console.log(total); // 10
dep.notify();
console.log(total); // 40Chúng ta vẫn còn có thể nâng cấp đoạn code trên, thay vì
let target = () => { total = price * quantity }
dep.depend();
target();... chúng ta đóng gói nó vào một watcher, sau đó chỉ cần gọi
watcher(() => {
total = price * quantity
})Implement cái function watcher này như bên dưới
function watcher(myFunc) {
target = myFunc; // active target, target ở đây là global variable
dep.depend(); // đưa target vào dependency
target(); // gọi hàm target
target = null; // reset
}Tách Dep cho mỗi biến
Chúng ta sẽ muốn mỗi một biến có một Dep riêng, trước tiên ta đưa price và quantity thành property của data
let data = {price: 5, quantity: 2}Chúng ta sẽ có các Dep khác nhau cho price và quantity

watcher phụ thuộc cả 2 biến
watcher(() => {
total = data.price * data.quantity;
})
watcher chỉ phụ thuộc biến price
watcher(() => {
salePrice = data.price * 0.9;
})
Chúng ta muốn khi giá trị price bị thay đổi, hàm dep.notify của price store sẽ được gọi
>> total
10
>> price = 20 // lúc này thằng notify của price sẽ được gọi liên luôn
>> total
40Đọc thêm tài liệu về Object.defineProperty nếu chưa biết. Áp dụng nó trong ví dụ này
let data = {price: 5, quantity: 2}
let internalValue = data.price; // giá trị khởi tạo
Object.defineProperty(data, 'price', { // chỉ cho thằng Price Property
get() {
console.log('Em bị access');
return internalValue;
},
set(newVal) {
console.log('Em bị thay đổi');
internalvalue = newVal;
}
})
data.price // call get()
data.price = 20 // call set()
total = data.price * data.quantity;
data.price = 20;Với cách này, chúng ta có thể chạy kèm một hàm nào đó khi giá trị price được get hoặc set. Với idea là như thế chúng ta tổng quát quá lên cho nhiều biến
let data = {price: 5, quantity: 2}
Object.keys(data).forEach(key => {
let intervalvalue = data[key];
Object.defineProperty(data, key, { // chỉ cho thằng Price Property
get() {
console.log('Em bị access');
return internalValue;
},
set(newVal) {
console.log('Em bị thay đổi');
internalvalue = newVal;
}
})
})
total = data.price * data.quantity;
data.price = 20;Tổng hợp các ý tưởng chính
total = data.price * data.quantityKhi một đoạn code như vậy được chạy, nó sẽ get giá trị của price, chúng ta muốn thẳng price khi bị thay đổi hoặc gọi, nó sẽ re-run một function
- Ở Get: nhớ dùm cái function này, bọn tao sẽ nhờ mày chạy lại
- Ở Set: chạy cái function mày đã giữ hộ ấy, thay đổi giá trị luôn nhé
Và đây là toàn bộ code
let data = {price: 5, quantity: 2};
let target = null;
// Dep không thay đổi gì so với ở trên
class Dep {
constructor() {
// thay vì là starage, thiên hạ đã thống nhất lấy cái tên subscribers
this.subscribers = [];
}
depend() {
if (target && !this.subscribers.includes(target)) {
// chỉ thêm vào nếu chưa có hoặc không trùng
thiss.subscribers.push(target);
}
}
notify() {
// run tất cả target, tên gọi khác là observer
this.subscribers.forEach(sub => sub());
}
}
// chạy qua từng data của property
Object.keys(data).forEach(key => {
let intervalvalue = data[key];
// mỗi em một Dep
const dep = new Dep();
Object.defineProperty(data, key, { // chỉ cho thằng Price Property
get() {
dep.depend(); // lưu hộ tao cái
return internalValue;
},
set(newVal) {
internalvalue = newVal;
dep.notify();// re-run đi em
}
})
})
// watcher sẽ không còn gọi dep.depend nữa
function watcher(myFunc) {
target = myFunc;
target();
target = null;
}
watcher(() => {
data.total = data.price * data.quantity;
})Kết quả nè

Hình mình họa lấy từ Vue





Initializing...