Javascript Proxy, Reflect và câu chuyện reactive

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 pricequantity? 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 pricequantity thay đổi.

Và để làm được điều đó, chúng ta sẽ định nghĩa pricequantity trong một object. Kiểu như sau:

const state = {
    price: 5,
    quantity: 2
}

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 gettersetter 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;
}

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