Chào mừng đến với Cafedev! Trong cuộc hành trình khám phá Vue.js cùng chúng tôi, chúng ta sẽ đi sâu vào một trong những tính năng phổ biến nhất của nó: hệ thống phản ứng. Vuejs không chỉ là một thư viện JavaScript mạnh mẽ, mà còn là một hệ thống phản ứng tinh tế. Trong chủ đề này, chúng ta sẽ khám phá cách Vuejs xử lý sự phản ứng một cách chi tiết và tỉ mỉ, giúp bạn hiểu rõ hơn về cách hoạt động của nó và tránh được những rủi ro phổ biến. Hãy cùng bắt đầu hành trình khám phá mới này!

Một trong những đặc điểm độc đáo nhất của Vue là hệ thống phản ứng không làm phiền. Trạng thái của thành phần bao gồm các đối tượng JavaScript phản ứng. Khi bạn sửa đổi chúng, giao diện người dùng được cập nhật. Nó làm cho việc quản lý trạng thái trở nên đơn giản và dễ hiểu, nhưng cũng quan trọng là hiểu cách nó hoạt động để tránh một số vấn đề phổ biến. Trong phần này, chúng ta sẽ đào sâu vào một số chi tiết cấp thấp của hệ thống phản ứng của Vue.

1. Reactivity là gì?

Thuật ngữ này xuất hiện trong lập trình khá nhiều ngày nay, nhưng khi mọi người nói về nó, họ có ý gì? Phản ứng là một mô hình lập trình cho phép chúng ta thích ứng với các thay đổi một cách tuyên bố. Ví dụ cânonic mà mọi người thường trình bày, vì nó là một ví dụ tuyệt vời, là một bảng tính Excel:

ABC
01
12
23



Ở đây, ô A2 được định nghĩa thông qua một công thức là = A0 + A1 (bạn có thể nhấp vào A2 để xem hoặc chỉnh sửa công thức), vì vậy bảng tính cung cấp cho chúng ta 3. Không có gì bất ngờ ở đây. Nhưng nếu bạn cập nhật A0 hoặc A1, bạn sẽ nhận thấy rằng A2 tự động cập nhật theo.
JavaScript thường không hoạt động như vậy. Nếu chúng ta muốn viết một cái gì đó tương tự trong JavaScript:

let A0 = 1
let A1 = 2
let A2 = A0 + A1

console.log(A2) // 3

A0 = 2
console.log(A2) // Still 3

Khi chúng ta biến đổi A0, A2 không thay đổi tự động.
Vậy làm thế nào để làm điều này trong JavaScript? Đầu tiên, để chạy lại mã cập nhật A2, hãy bọc nó trong một hàm:

let A2

function update() {
  A2 = A0 + A1
}

Sau đó, chúng ta cần định nghĩa một số thuật ngữ: – Hàm update() tạo ra một hiệu ứng phụ, hoặc hiệu ứng cho ngắn gọn, vì nó sửa đổi trạng thái của chương trình.
– Hàm update() tạo ra một hiệu ứng phụ, hoặc hiệu ứng cho ngắn gọn, vì nó sửa đổi trạng thái của chương trình.

  • A0A1 được coi là phụ thuộc của hiệu ứng, vì giá trị của chúng được sử dụng để thực hiện hiệu ứng. Hiệu ứng được gọi là người đăng ký của các phụ thuộc của nó.
    Điều chúng ta cần là một hàm kỳ diệu có thể gọi update() (hiệu ứng) mỗi khi A0 hoặc A1 (các phụ thuộc) thay đổi:
whenDepsChange(update)

Hàm whenDepsChange() có các nhiệm vụ sau:

  • Theo dõi khi một biến được đọc. Ví dụ, khi đánh giá biểu thức A0 + A1, cả A0A1 đều được đọc.
  • Nếu một biến được đọc khi hiệu ứng đang chạy, làm cho hiệu ứng đó trở thành một người đăng ký cho biến đó. Ví dụ, vì A0A1 được đọc khi update() đang được thực thi, update() trở thành một người đăng ký cho cả A0A1 sau lần gọi đầu tiên.
  • Phát hiện khi một biến bị biến đổi. Ví dụ, khi A0 được gán một giá trị mới, thông báo cho tất cả các hiệu ứng người đăng ký của nó để chạy lại.

2. Cách Phản ứng Hoạt động trong Vue

Thực ra, chúng ta không thể theo dõi việc đọc và ghi các biến cục bộ như trong ví dụ. Không có cơ chế nào cho điều đó trong JavaScript thuần túy. Tuy nhiên, điều chúng ta có thể làm là chặn việc đọc và ghi các thuộc tính của đối tượng.
Có hai cách để chặn truy cập thuộc tính trong JavaScript: getter / setterProxies. Vue 2 chỉ sử dụng getter / setters do giới hạn hỗ trợ trình duyệt. Trong Vue 3, Proxies được sử dụng cho các đối tượng phản ứng và getter / setters được sử dụng cho các refs. Dưới đây là một đoạn mã giả mạo minh họa cách chúng hoạt động:

{4,9,17,22}
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}

function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

Lưu ý: Các đoạn mã ở đây và dưới đây được thiết kế để giải thích các khái niệm cốt lõi một cách đơn giản nhất có thể, vì vậy nhiều chi tiết được bỏ qua, và các trường hợp biên được bỏ qua.

Điều này giải thích một số hạn chế của các đối tượng phản ứng mà chúng ta đã thảo luận trong phần cơ bản:

  • Khi bạn gán hoặc giải cấu trúc một thuộc tính của đối tượng phản ứng cho một biến cục bộ, việc truy cập hoặc gán cho biến đó là không phản ứng vì nó không kích hoạt các trạng giác lưới getter / setter trên đối tượng nguồn. Lưu ý rằng sự “tách rời” này chỉ ảnh hưởng đến ràng buộc biến – nếu biến trỏ đến một giá trị không nguyên thủy như một đối tượng, việc biến đổi đối tượng vẫn sẽ phản ứng.
  • Proxy trả về từ reactive(), mặc dù hoạt động giống như ban đầu, nhưng có một định danh khác nếu chúng ta so sánh nó với ban đầu bằng toán tử ===.
    Trong track(), chúng ta kiểm tra xem có hiệu ứng đang chạy hiện tại không. Nếu có, chúng ta tìm kiếm các hiệu ứng đăng ký (được lưu trữ trong một Set) cho thuộc tính đang được theo dõi và thêm hiệu ứng vào Set:
// This will be set right before an effect is about
// to be run. We'll deal with this later.
let activeEffect

function track(target, key) {
  if (activeEffect) {
    const effects = getSubscribersForProperty(target, key)
    effects.add(activeEffect)
  }
}

Các đăng ký hiệu ứng được lưu trữ trong một cấu trúc dữ liệu WeakMap<target, map<key,="" set<effect="">>></target,> toàn cục. Nếu không tìm thấy Bộ hiệu ứng đăng ký nào cho một thuộc tính (theo dõi lần đầu tiên), nó sẽ được tạo ra. Đó là những gì hàm getSubscribersForProperty() thực hiện, một cách tóm tắt. Để đơn giản, chúng tôi sẽ bỏ qua chi tiết của nó.
Trong trigger(), chúng ta lại tìm kiếm các hiệu ứng đăng ký cho thuộc tính. Nhưng lần này chúng ta gọi chúng thay vì gọi chúng:

function trigger(target, key) {
  const effects = getSubscribersForProperty(target, key)
  effects.forEach((effect) => effect())
}

Bây giờ hãy quay lại hàm whenDepsChange():

function whenDepsChange(update) {
  const effect = () => {
    activeEffect = effect
    update()
    activeEffect = null
  }
  effect()
}

Nó bao bọc hàm update thô trong một hiệu ứng mà đặt chính nó làm hiệu ứng hoạt động hiện tại trước khi chạy cập nhật thực tế. Điều này cho phép cuộc gọi track() trong quá trình cập nhật để xác định hiệu ứng hoạt động hiện tại.
Tại điểm này, chúng ta đã tạo ra một hiệu ứng mà tự động theo dõi các phụ thuộc của nó và chạy lại mỗi khi một phụ thuộc thay đổi. Chúng ta gọi điều này là một Hiệu ứng Phản ứng.

Vue cung cấp một API cho phép bạn tạo hiệu ứng phản ứng: watchEffect(). Trong thực tế, bạn có thể đã nhận ra rằng nó hoạt động khá giống với hàm whenDepsChange() kỳ diệu trong ví dụ. Bây giờ chúng ta có thể làm lại ví dụ ban đầu bằng cách sử dụng các API Vue thực tế:

import { ref, watchEffect } from 'vue'

const A0 = ref(0)
const A1 = ref(1)
const A2 = ref()

watchEffect(() => {
  // tracks A0 and A1
  A2.value = A0.value + A1.value
})

// triggers the effect
A0.value = 2

Sử dụng một hiệu ứng phản ứng để biến đổi một ref không phải là trường hợp sử dụng thú vị nhất – thực tế, sử dụng một thuộc tính tính toán làm cho nó trở nên rõ ràng hơn:

import { ref, computed } from 'vue'

const A0 = ref(0)
const A1 = ref(1)
const A2 = computed(() => A0.value + A1.value)

A0.value = 2

Bên trong, computed quản lý sự vô hiệu hóa và việc tính toán lại của nó bằng cách sử dụng một hiệu ứng phản ứng.
Vậy ví dụ về một hiệu ứng phản ứng phổ biến và hữu ích là gì? Như vậy, cập nhật DOM! Chúng ta có thể triển khai “k rendering phản ứng” đơn giản như sau:

import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(() => {
  document.body.innerHTML = `count is: ${count.value}`
})

// updates the DOM
count.value++

Thực tế, điều này khá gần với cách một thành phần Vue duy trì trạng thái và DOM đồng bộ – mỗi phiên bản thành phần tạo ra một hiệu ứng phản ứng để vẽ và cập nhật DOM. Tất nhiên, các thành phần Vue sử dụng cách hiệu quả hơn để cập nhật DOM hơn là innerHTML. Điều này được thảo luận trong Cơ chế Vẽ.
Các API ref(), computed()watchEffect() đều là một phần của API Hợp thành. Nếu bạn chỉ sử dụng API Tùy chọn với Vue cho đến nay, bạn sẽ nhận thấy rằng API Hợp thành gần với cách hệ thống phản ứng của Vue hoạt động dưới nền. Trong thực tế, trong Vue 3, API Tùy chọn được triển khai trên cơ sở của API Hợp thành. Tất cả việc truy cập thuộc tính trên phiên bản thành phần (this) kích hoạt getter / setter để theo dõi tính phản ứng, và các tùy chọn như watchcomputed gọi các phiên bản tương đương của API Hợp thành của chúng bên trong.

3. Sự Phản ứng Tại Thời gian Chạy và Tại Thời gian Biên dịch

Hệ thống phản ứng của Vue chủ yếu là dựa trên thời gian chạy: việc theo dõi và kích hoạt đều được thực hiện trong khi mã đang chạy trực tiếp trong trình duyệt. Các ưu điểm của phản ứng tại thời gian chạy là nó có thể hoạt động mà không cần bước xây dựng, và có ít trường hợp biên. Tuy nhiên, điều này làm cho nó bị ràng buộc bởi các hạn chế cú pháp của JavaScript, dẫn đến sự cần thiết của các bộ chứa giá trị như Vue refs.
Một số framework, như Svelte, chọn giải quyết các hạn chế này bằng cách triển khai phản ứng trong quá trình biên dịch. Nó phân tích và biến đổi mã để mô phỏng phản ứng. Bước biên dịch cho phép framework thay đổi ngữ nghĩa của JavaScript chính nó – ví dụ, tự động tiêm mã thực hiện phân tích phụ thuộc và kích hoạt hiệu ứng xung quanh việc truy cập vào các biến được định nghĩa cục bộ. Hạn chế là các biến đổi như vậy đòi hỏi một bước xây dựng, và việc thay đổi ngữ nghĩa của JavaScript về cơ bản là tạo ra một ngôn ngữ giống như JavaScript nhưng biên dịch thành một cái gì đó khác.

Nhóm phát triển Vue đã khám phá hướng này thông qua một tính năng thử nghiệm gọi là Biến đổi Phản ứng, nhưng cuối cùng chúng tôi đã quyết định rằng nó sẽ không phù hợp với dự án do lý do ở đây.

4. Gỡ lỗi Phản ứng

Rất tuyệt vời khi hệ thống phản ứng của Vue tự động theo dõi các phụ thuộc, nhưng trong một số trường hợp chúng ta có thể muốn tìm hiểu chính xác cái gì đang được theo dõi, hoặc cái gì đang gây ra việc kích hoạt lại một thành phần.

4.1 Các Kết nối Gỡ lỗi Thành phần

Chúng ta có thể gỡ lỗi xem các phụ thuộc nào được sử dụng trong quá trình render của một thành phần và phụ thuộc nào đang kích hoạt một cập nhật bằng cách sử dụng các hooks vòng đời renderTrackedonRenderTrackedrenderTriggeredonRenderTriggered. Cả hai hooks đều sẽ nhận một sự kiện gỡ lỗi chứa thông tin về phụ thuộc đang được xem xét. Đề xuất đặt một lệnh debugger trong các hàm gọi lại để kiểm tra tương tác với phụ thuộc một cách tương tác:

<script setup>
import { onRenderTracked, onRenderTriggered } from 'vue'

onRenderTracked((event) => {
  debugger
})

onRenderTriggered((event) => {
  debugger
})
</script>
export default {
  renderTracked(event) {
    debugger
  },
  renderTriggered(event) {
    debugger
  }
}

Gỡ lỗi hooks của thành phần chỉ hoạt động trong chế độ phát triển.
Các đối tượng sự kiện gỡ lỗi có kiểu sau:

type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type:
    | TrackOpTypes /* 'get' | 'has' | 'iterate' */
    | TriggerOpTypes /* 'set' | 'add' | 'delete' | 'clear' */
  key: any
  newValue?: any
  oldValue?: any
  oldTarget?: Map<any, any> | Set<any>
}

4.2 Gỡ lỗi Computed

Chúng ta có thể gỡ lỗi các thuộc tính computed bằng cách truyền cho computed() một đối tượng tùy chọn thứ hai với các hàm gọi lại onTrackonTrigger:
onTrack sẽ được gọi khi một thuộc tính phản ứng hoặc ref được theo dõi như một phụ thuộc.
onTrigger sẽ được gọi khi hàm gọi của watcher được kích hoạt bởi sự biến đổi của một phụ thuộc.
Cả hai hàm gọi lại sẽ nhận sự kiện gỡ lỗi trong cùng định dạng như các hooks gỡ lỗi của thành phần:

const plusOne = computed(() => count.value + 1, {
  onTrack(e) {
    // triggered when count.value is tracked as a dependency
    debugger
  },
  onTrigger(e) {
    // triggered when count.value is mutated
    debugger
  }
})

// access plusOne, should trigger onTrack
console.log(plusOne.value)

// mutate count.value, should trigger onTrigger
count.value++

Gỡ lỗi tùy chọn onTrackonTrigger của computed chỉ hoạt động trong chế độ phát triển.

4.3 Gỡ lỗi Watcher

Tương tự như computed(), watchers cũng hỗ trợ các tùy chọn onTrackonTrigger:

watch(source, callback, {
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

watchEffect(callback, {
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

Gỡ lỗi tùy chọn onTrackonTrigger của watcher chỉ hoạt động trong chế độ phát triển.

5. Tích hợp với Hệ thống Trạng thái Bên ngoài

Hệ thống phản ứng của Vue hoạt động bằng cách chuyển đổi sâu các đối tượng JavaScript đơn giản thành các proxy phản ứng. Việc chuyển đổi sâu có thể là không cần thiết hoặc đôi khi không mong muốn khi tích hợp với các hệ thống quản lý trạng thái bên ngoài (ví dụ: nếu một giải pháp bên ngoài cũng sử dụng Proxies).

6. Tích hợp với Hệ thống Trạng thái Bên ngoài

Hệ thống phản ứng của Vue hoạt động bằng cách chuyển đổi sâu các đối tượng JavaScript đơn giản thành các proxy phản ứng. Việc chuyển đổi sâu có thể là không cần thiết hoặc đôi khi không mong muốn khi tích hợp với các hệ thống quản lý trạng thái bên ngoài (ví dụ: nếu một giải pháp bên ngoài cũng sử dụng Proxies).
Ý tưởng chung của việc tích hợp hệ thống phản ứng của Vue với một giải pháp quản lý trạng thái bên ngoài là giữ trạng thái bên ngoài trong một shallowRef. Một shallow ref chỉ phản ứng khi thuộc tính .value của nó được truy cập – giá trị bên trong được bảo toàn nguyên vẹn. Khi trạng thái bên ngoài thay đổi, hãy thay đổi giá trị của ref để kích hoạt cập nhật.

6.1 Dữ liệu Bất biến

Nếu bạn đang triển khai một tính năng undo/redo, bạn có thể muốn chụp ảnh của trạng thái ứng dụng sau mỗi lần người dùng chỉnh sửa. Tuy nhiên, hệ thống phản ứng có khả năng biến đổi của Vue không phải là lựa chọn tốt nhất cho điều này nếu cây trạng thái lớn, vì việc tuần tự hóa toàn bộ đối tượng trạng thái sau mỗi lần cập nhật có thể tốn kém về cả CPU và bộ nhớ.
Cấu trúc dữ liệu bất biến giải quyết vấn đề này bằng cách không bao giờ biến đổi các đối tượng trạng thái – thay vào đó, nó tạo ra các đối tượng mới chia sẻ các phần không thay đổi với các đối tượng cũ. Có các cách khác nhau để sử dụng dữ liệu bất biến trong JavaScript, nhưng chúng tôi khuyên bạn nên sử dụng Immer với Vue vì nó cho phép bạn sử dụng dữ liệu bất biến trong khi vẫn giữ nguyên cú pháp thoải mái, có thể biến đổi.

Chúng ta có thể tích hợp Immer với Vue thông qua một composable đơn giản:

import { produce } from 'immer'
import { shallowRef } from 'vue'

export function useImmer(baseState) {
  const state = shallowRef(baseState)
  const update = (updater) => {
    state.value = produce(state.value, updater)
  }

  return [state, update]
}

Thử nghiệm ở Playground

6.2 Máy Trạng Thái

Máy Trạng Thái là một mô hình để mô tả tất cả các trạng thái có thể của ứng dụng, và tất cả các cách có thể chuyển từ một trạng thái này sang trạng thái khác. Mặc dù nó có thể là quá mức cho các thành phần đơn giản, nhưng nó có thể giúp làm cho các luồng trạng thái phức tạp trở nên mạnh mẽ và dễ quản lý hơn.
Một trong những thư viện máy trạng thái phổ biến nhất trong JavaScript XState. Dưới đây là một composable tích hợp với nó:

import { createMachine, interpret } from 'xstate'
import { shallowRef } from 'vue'

export function useMachine(options) {
  const machine = createMachine(options)
  const state = shallowRef(machine.initialState)
  const service = interpret(machine)
    .onTransition((newState) => (state.value = newState))
    .start()
  const send = (event) => service.send(event)

  return [state, send]
}

Thử nghiệm ở Playground

6.3 RxJS

RxJS là một thư viện để làm việc với các luồng sự kiện bất đồng bộ. Thư viện VueUse cung cấp @vueuse/rxjs add-on để kết nối các luồng RxJS với hệ thống phản ứng của Vue.

7. Kết nối với Tín hiệu

Nhiều framework khác đã giới thiệu các nguyên tố phản ứng tương tự như refs từ Composition API của Vue, dưới thuật ngữ “tín hiệu”:
Solid Signals
Angular Signals
Preact Signals
Qwik Signals
Về cơ bản, các tín hiệu là cùng một loại nguyên tố phản ứng như các refs của Vue. Đó là một container giá trị cung cấp việc theo dõi phụ thuộc khi truy cập và kích hoạt tác động phụ khi biến đổi. Mô hình dựa trên nguyên tắc nguyên thủy này không phải là một khái niệm mới lạ trong thế giới frontend: nó trở lại các thực hiện như Knockout observablesMeteor Tracker từ hơn một thập kỷ trước. Vue Options API và thư viện quản lý trạng thái React MobX cũng dựa trên cùng nguyên lý, nhưng ẩn các nguyên thủy sau các thuộc tính đối tượng.
Mặc dù không phải là một đặc điểm cần thiết để một điều gì đó được xem là tín hiệu, nhưng hiện nay khái niệm này thường được thảo luận song song với mô hình kết xuất nơi các cập nhật được thực hiện thông qua các đăng ký tinh tế. Do sử dụng Virtual DOM, Vue hiện đang phụ thuộc vào trình biên dịch để đạt được các tối ưu hóa tương tự. Tuy nhiên, chúng tôi cũng đang khám phá một chiến lược biên dịch mới lấy cảm hứng từ Solid (Chế độ Hơi) mà không phụ thuộc vào Virtual DOM và tận dụng nhiều hơn hệ thống phản ứng tích hợp của Vue.

7.1 Sự đánh đổi thiết kế API

Thiết kế của tín hiệu Preact và Qwik rất giống với shallowRef của Vue: cả ba đều cung cấp một giao diện có thể biến đổi qua thuộc tính .value. Chúng tôi sẽ tập trung thảo luận về tín hiệu Solid và Angular.

Tín hiệu Solid

Thiết kế API createSignal() của Solid nhấn mạnh việc phân chia đọc / ghi. Tín hiệu được tiết lộ dưới dạng một getter chỉ đọc và một setter riêng biệt:

const [count, setCount] = createSignal(0)

count() // access the value
setCount(1) // update the value

Lưu ý cách tín hiệu count có thể được truyền xuống mà không cần setter. Điều này đảm bảo rằng trạng thái không bao giờ bị biến đổi trừ khi setter cũng được tiết lộ một cách rõ ràng. Việc đảm bảo an toàn này có xứng đáng với cú pháp chi tiết hơn có thể phụ thuộc vào yêu cầu của dự án và sở thích cá nhân – nhưng trong trường hợp bạn ưa thích phong cách API này, bạn có thể dễ dàng sao chép nó trong Vue:

import { shallowRef, triggerRef } from 'vue'

export function createSignal(value, options) {
  const r = shallowRef(value)
  const get = () => r.value
  const set = (v) => {
    r.value = typeof v === 'function' ? v(r.value) : v
    if (options?.equals === false) triggerRef(r)
  }
  return [get, set]
}

Thử nghiệm ở Playground

Tín hiệu Angular

Angular đang trải qua một số thay đổi cơ bản bằng cách bỏ qua dirty-checking và giới thiệu triển khai riêng của một nguyên tố phản ứng. API Tín hiệu Angular trông như sau:

const count = signal(0)

count() // access the value
count.set(1) // set new value
count.update((v) => v + 1) // update based on previous value

Một lần nữa, chúng ta có thể dễ dàng sao chép API trong Vue:

import { shallowRef } from 'vue'

export function signal(initialValue) {
  const r = shallowRef(initialValue)
  const s = () => r.value
  s.set = (value) => {
    r.value = value
  }
  s.update = (updater) => {
    r.value = updater(r.value)
  }
  return s
}

Thử nghiệm ở Playground
So với refs của Vue, phong cách API dựa trên getter của Solid và Angular cung cấp một số đánh đổi thú vị khi sử dụng trong các thành phần Vue:
() ít chi tiết hơn .value, nhưng cập nhật giá trị mất nhiều công đoạn hơn.
– Không có việc mở gói refs: việc truy cập giá trị luôn yêu cầu (). Điều này làm cho việc truy cập giá trị nhất quán ở mọi nơi. Điều này cũng có nghĩa là bạn có thể truyền tín hiệu gốc xuống như các props của thành phần.
Việc phù hợp với bạn trong mức độ nào là phụ thuộc vào cá nhân một phần. Mục tiêu của chúng tôi ở đây là để thể hiện sự tương đồng cơ bản và sự đánh đổi giữa các thiết kế API khác nhau này. Chúng tôi cũng muốn cho thấy rằng Vue là linh hoạt: bạn thực sự không bị ràng buộc vào các API hiện có. Nếu cần thiết, bạn có thể tạo ra API nguyên thủy phản ứng riêng của mình để phù hợp với nhu cầu cụ thể hơn.

Cảm ơn bạn đã tham gia cùng chúng tôi trong hành trình khám phá sâu về Vue.js tại Cafedev. Hy vọng rằng bạn đã có được cái nhìn tổng quan và chi tiết về hệ thống phản ứng của Vuejs thông qua nội dung chia sẻ. Đừng ngần ngại áp dụng những kiến thức mới này vào dự án của bạn và tiếp tục khám phá thêm về sức mạnh của Vuejs. Hãy tiếp tục theo dõi Cafedev để cập nhật thêm thông tin và bài viết hữu ích khác về công nghệ và lập trình!

Tham khảo thêm: MIỄN PHÍ 100% | Series tự học Vuejs từ cơ bản tới nâng cao

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!