Reactivity là gì? Học nhanh Proxy và Reflect qua ví dụ thực tế

Châm ngôn của mình là học để kiếm tiền.

Vì thế mình build các khóa học của mình để giúp anh em tiến bộ nhanh hơn x10 lần , để kiếm được nhiều tiền hơn

  • 🏆 React.js Super: Trở thành React.js Developer trong 7 ngày với mức thu nhập 20 triệu/tháng
  • 🏆 Node.js Super: Giúp bạn học cách phân tích, thiết kế, deploy 1 API Backend bằng Node.js
  • 🏆 Next.js Super: Mình sẽ chia sẻ từ A-Z kiến thức về Next.js, thứ giúp mình kiếm hơn 1 tỉ/năm

Bây giờ là lúc để đi tìm hiểu sâu hơn vào bên trong các framework JS. Một trong những tính năng đặc biệt nhất của Vue hay Angular là hệ thống reactivity. Khi bạn thay đổi state thì view sẽ tự động được cập nhật. Điều này làm cho việc quản lý state trở nên dễ dàng và trực quan.

💡 Mẹo:

React không phải là thư viện reactive nhé. Bạn thay đổi state và gọi setState thì view mới cập nhật được, tức là bạn phải cập nhật bằng tay.

🥇Reactivity là gì?

Reactivity là một mô hình lập trình cho phép chúng ta phản ứng lại những sự thay đổi biến. Excel sheet dưới đây là một ví dụ kinh điển về nó.

Nếu bạn nhập số 2 ở ô thứ nhất, số 3 ở ô thứ 2 và thực hiện SUM, sheet sẽ cho bạn giá trị tổng. Không có bất ngờ nào ở đây. Nhưng nếu bạn update giá trị thứ nhất, SUM cũng sẽ tự động update lại.

Javascript thì thường không hoạt động giống như vậy. Cùng viết thứ một đoạn bằng JS nhé.

js
let val1 = 2
let val2 = 3
let sum = val1 + val2
console.log(sum) // 5
val1 = 3
console.log(sum) // Still 5

Nếu chúng ta update giá trị đầu tiên, sum sẽ không bị thay đổi.

Vậy chúng ta làm điều này trong Javascript như thế nào?

Tạo một function handle và gọi lại bằng tay mỗi khi val1 hoặc val2 thay đổi ư, quá mệt mỏi!

js
let val1 = 2
let val2 = 3
let sum
const handle = () => {
  sụm = val1 + val2
}
handle()
console.log(sum) // 5
val1 = 3
sum()
console.log(sum) // 6

Có một vài thứ chúng ta có thể làm là:

  1. Giám sát các giá trị. Ví dụ val1 + val2 thì giám sát cả 2 val1val2
  2. Phát hiện khi một giá trị thay đổi. Ví dụ chúng ta gán val1 = 3.
  3. Chạy lại code. Ví dụ run sum = val1 + val2 lại để cập nhật giá trị sum.

Và đây là cách giải quyết đơn giản.

js
let data = {
  val1: 2,
  val2: 3
}
let sum
const handle = () => {
  sum = data.val1 + data.val2
}
handle()
Object.keys(data).forEach((key) => {
  let internalValue = data[key]
  Object.defineProperty(data, key, {
    get() {
      return internalValue
    },
    set(newValue) {
      internalValue = newValue
      handle()
    }
  })
})
console.log(sum) // 5
data.val1 = 4
console.log(sum) // 7

Đầu tiên mình sẽ đưa những giá trị mình cần giám sát vào trong object data. Mình sẽ dùng Object.defineProperty để chuyển các thuộc tính dữ liệu của data thành thuộc tính bộ truy cập (getter / setter đó).

💡 Mẹo:

Các bạn có thể đọc thêm Property getters and setters để hiểu rõ hơn.

Và mỗi khi mình thực hiện thay đổi thuộc tính thì setter sẽ chạy và function handle() được thực thi.

Thế thôi, đơn giản mà đúng không

Nhưng ES6 cung cấp cho bạn một tính năng hay hơn chuyên để xử lý những vấn đề này, đó là Proxy.

🥇Proxy là gì?

Proxy là một object thường được sử dụng để chỉnh sửa các hành vi của các toán tử cơ bản cho object.

Proxy thường được coi như là một trung gian giữa object và các hành vi gán, sửa, xóa… trên object.

Proxy được tạo với 2 tham số:

  • target: object gốc mà bạn muốn proxy
  • handler: một object chứa các trap. Trap là các phương thức để đáp ứng lại các thao tác dữ liệu trên target.

Quay trở lại bài toán bên trên, ta có thể giải quyết bằng proxy như sau

js
let data = {
  val1: 2,
  val2: 3
}
let sum
const handle = () => {
  sum = data.val1 + data.val2
}
handle()
console.log(sum) // 5
const proxy = new Proxy(data, {
  get(target, prop) {
    return target[prop]
  },
  set(obj, prop, value) {
    obj[prop] = value
    handle()
    // Phải return true để cho Engine biết là gán hoàn thành
    // Nếu return false thì trong strict-mode sẽ bị lỗi TypeError
    return true
  }
})
proxy.val1 = 3
console.log(sum) // 6

Chúng ta cũng có thể xóa get() đi cũng được.

js
const proxy = new Proxy(data, {
  set(obj, prop, value) {
    obj[prop] = value
    handle()
    return true
  }
})

get()set() bên trên được gọi là các trap.

Dưới đây là danh sách các trap theo trang mozilla

  • handler.apply(): A trap for a function call.

  • handler.construct(): A trap for the new operator.

  • handler.defineProperty(): A trap for Object.defineProperty.

  • handler.deleteProperty(): A trap for the delete operator.

  • handler.get(): A trap for getting property values.

  • handler.getOwnPropertyDescriptor(): A trap for Object.getOwnPropertyDescriptor.

  • handler.getPrototypeOf(): A trap for Object.getPrototypeOf.

  • handler.has(): A trap for the in operator.

  • handler.isExtensible(): A trap for Object.isExtensible.

  • handler.ownKeys(): A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.

  • handler.preventExtensions(): A trap for Object.preventExtensions.

  • handler.set(): A trap for setting property values.

  • handler.setPrototypeOf(): A trap for Object.setPrototypeOf.

🥇Tạo thử Reactive DOM như Vue 3

Nếu bạn đã code Vue thì bạn sẽ thấy hệ thống reactivity của Vue khá xịn, thay đổi data là nó sẽ giúp chúng ta cập nhật DOM luôn. Bây giờ mình sẽ mô phỏng lại tính năng đó thử xem sao nhé.

Khá là hay ho :mrgreen: .

Cho bạn nào chưa biết thì Vue 2 dùng Object.defineProperty, còn Vue 3 đã nâng cấp lên dùng Proxy rồi.

Nhắc đến Proxy thì không thể nào quên Reflect được, cùng mình tìm hiểu thử Reflect là gì nhé.

🥇Reflect là gì?

Reflect là một object được cung cấp sẵn trong Javascript thường được dùng để phối hợp với proxy handler. Không như hầu hết các global object thì Reflect không phải là một constructor function, vì thế không thể tạo mới object với toán tử new.

Reflect cung cấp cho chúng ta các phương thức tương tự như bên Object.

js
const target = {
  name: 'Dư Thanh Được'
}
// Tương tự Object.defineProperty
const result = Reflect.defineProperty(target, 'age', { value: 25 })
if (result) {
  // Thành công
} else {
  // Thất bại
}

Các phương thức của Reflect cũng tương ứng với các trap của Proxy, quay trở lại bài toán tính sum ban đầu, nếu áp dụng Reflect vào thì code sẽ như thế này.

js
const proxy = new Proxy(data, {
  get(...props) {
    Reflect.get(...props)
  },
  set(...props) {
    const result = Reflect.set(...props)
    handle()
    return result
  }
})

🥇Tóm lại

Hy vọng bài viết ngắn này của mình giúp ích cho các bạn hiểu rõ hơn về Reactivity cũng như ProxyReflect.

Dạo này mình cũng cố gắng siêng hơn một chút, ra bài viết nhiều hơn một chút nên mọi người đón đọc nhé. Hẹn mọi người ở các bài viết tiếp theo 😀

🥇Tham khảo


Kiến thức trong khóa học Next.js này đã giúp mình kiếm hơn 1 tỉ đồng/năm

Phew! Cuối cùng bạn cũng đã đọc xong. Bài viết này có hơi dài một tí vì mình muốn nó đầy đủ nhất có thể 😅

Website bạn đang đọc được viết bằng Next.js TypeScript và tối ưu từng chi tiết nhỏ như SEO, hiệu suất, nội dung để đảm bảo bạn có trải nghiệm tốt nhất.

Với lượt view trung bình là 30k/tháng (dù website rất ít bài viết). Website này đem lại doanh thu 1 năm vừa qua là hơn 1 tỉ đồng

Đó chính là sức mạnh của SEO, sức mạnh của Next.js.

Mình luôn tin rằng kiến thức là chìa khóa giúp chúng ta đi nhanh nhất.

Mình đã dành hơn 6 tháng để phát triển khóa học Next.js Super | Dự án quản lý quán ăn & gọi món bằng QR Code. Trong khóa này các bạn sẽ được học mọi thứ về framework Next.js, các kiến thức từ cơ bản cho đến nâng cao nhất, mục đích của mình là giúp bạn chinh phục mức lương 25 - 30 triêu/tháng

Nếu bạn cảm thấy bài viết này của mình hữu ích, mình nghĩ bạn sẽ thích hợp với phong cách dạy của mình. Không như bài viết này, khóa học là sự kết hợp giữa các bài viết, video, bài tập nhỏ và dự án lớn có thể xin việc được ngay. Học xong mình đảm bảo bạn sẽ lên tay ngay. 💪🏻

Avatar Dư Thanh Được

Dư Thanh Được

Một developer thích nghiên cứu và chia sẻ kiến thức về lập trình, blockchain, marketing. Chuyên code và dạy lập trình website