Javascript Proxy, Reflect và câu chuyện reactive
Bài đăng này đã không được cập nhật trong 5 năm
Bài toán
Giả sử chúng ta có công thức tính tổng số tiền dựa vào số lượng và giá của sản phẩm như sau:
let price = 5
let quantity = 2
let total = price * quantity // = 10
Nếu chúng ta thay đổi giá của sản phầm
price = 20
console.log(total) // 10
Thì lúc này total vẫn bằng 10. Bởi vì đó là cách mà javascript hoạt động.
Vậy có cách nào để làm giá trị của total này trở nên reactive. Tức là giá trị của nó sẽ thay đổi dựa vào price và quantity? Chúng ta hãy cùng xem tiếp bài viết nhé 
Lời giải
Mình sẽ đặt phép tính total ở trên trong một hàm và tạm gọi nó là một effect
const effect = () => {
total = price * quantity
}
Như vậy, để giải quyết bài toán trên, chúng ta sẽ tìm cách chạy lại hàm effect này mỗi khi price và quantity thay đổi.
Và để làm được điều đó, chúng ta sẽ định nghĩa price và quantity trong một object. Kiểu như sau:
const state = {
price: 5,
quantity: 2
}
Và effect của chúng ta sẽ trở thành
let effect = () => {
total = state.price * state.quantity
}
Tiếp theo chúng ta sẽ lặp qua từng property trong state object và sử dụng Object.defineProperty để định nghĩa lại getter và setter cho state như sau:
Object.keys(state).forEach(key => {
let internalValue = state[key] // Phải khởi tạo biến này với let
Object.defineProperty(state, key, {
get() {
return internalValue;
},
set(newValue) {
internalValue = newValue;
effect(); // lúc thay đổi giá trị sẽ chạy lại effect
}
})
})
Như vậy, khi chúng ta gán giá trị state.price = 10 chẳng hạn thì nó sẽ chạy lại hàm effect.
Và thế là total được update!
Hơn nữa, chúng ta có thể tạo ra một hàm để sử dụng lại cho các object khác như sau:
const reactive = data => {
let newData = {...data};
Object.keys(newData).forEach(key => {
let internalValue = newData[key]
Object.defineProperty(newData, key, {
get() {
return internalValue;
},
set(newValue) {
internalValue = newValue;
effect();
}
})
})
return newData;
}
Và state của chúng ta sẽ như sau:
const state = reactive({
price: 5,
quantity: 2
})
OK, ngon lành rồi.
Mà khoan, xong rồi sao. Sao chưa thấy nói gì đến Proxy ?
Đây!. Với Proxy chúng ta có thể định nghĩa lại hàm reactive một cách ngắn gọn hơn như sau:
const reactive = data => {
return new Proxy(data, {
get(target, key, receiver) {
return target[key]
},
set(target, key, value, receiver) {
target[key] = value;
effect()
}
})
}
Và nếu dùng Reflect:
const reactive = data => {
return new Proxy(data, {
get(target, key, receiver) {
return Reflect.get(...arguments) // arguments trong mỗi function đều có mn nhé :)
},
set(target, key, value, receiver) {
Reflect.set(...arguments)
effect()
}
})
}
Bây giờ nếu khởi tạo state bằng reactive, chúng ta sẽ thu được một Proxy như sau:
OK, xịn xò.
Thật ra Proxy không chỉ giúp cho code của chúng ta ngắn gọn hơn mà còn giúp trong việc xử lý reactive với array,...
Ở bài viết này, mình chỉ giới thiệu khái quát một phần về reactive. Chúng ta có thể thấy effect trong thực tế cũng phải được lưu lại và tính toán như thế nào đó để có thể sử dụng nhiều effect cùng lúc.
Bài viết đến đây thôi, thật ra mình cũng không hiểu lắm đâu. Các bạn có thể tìm hiểu kỹ hơn ở link bên dưới nhé. Chúc các bạn thành công 
Tài liệu tham khảo
https://www.vuemastery.com/courses/vue-3-reactivity/vue3-reactivity https://www.vuemastery.com/courses/advanced-components/build-a-reactivity-system
All rights reserved