Appearance
Cơ Bản về reactivity
Ưu Tiên API
Trang này và nhiều chương sau trong hướng dẫn chứa nội dung khác nhau cho Options API và Composition API. Ưu tiên hiện tại của bạn là Composition API. Bạn có thể chuyển đổi giữa các kiểu API bằng cách sử dụng các nút "API Preference" ở đầu thanh bên trái.
Khai báo state trong Vue
ref()
Trong Composition API, cách khuyến khích để khai báo reactive state là sử dụng hàm ref()
:
js
import { ref } from 'vue'
const count = ref(0)
ref()
lấy đối số và trả về nó được bọc trong một đối tượng ref với một thuộc tính .value
:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Xem thêm: Gán Kiểu cho Refs
Để truy cập các ref trong template của một component, hãy khai báo và trả chúng từ hàm setup()
của một component:
js
import { ref } from 'vue'
export default {
// `setup` là một hook đặc biệt dành cho Composition API.
setup() {
const count = ref(0)
// export ref cho mẫu
return {
count
}
}
}
template
<div>{{ count }}</div>
Lưu ý rằng chúng ta không cần phải thêm .value
khi sử dụng ref trong template. Đối với sự thuận tiện, refs tự động được giải gói khi sử dụng trong mẫu (với một số lưu ý).
Bạn cũng có thể biến đổi một ref trực tiếp trong khi xử lý event:
template
<button @click="count++">
{{ count }}
</button>
Đối với logic phức tạp hơn, chúng ta có thể khai báo các hàm biến đổi ref trong cùng phạm vi và export chúng như các phương thức cùng với trạng thái:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// .value cần thiết trong JavaScript
count.value++
}
// đừng quên export hàm nữa.
return {
count,
increment
}
}
}
Các phương thức được export sau đó có thể được sử dụng như khi xử lý event:
template
<button @click="increment">
{{ count }}
</button>
Dưới đây là ví dụ trực tiếp trên Codepen, mà không sử dụng bất kỳ công cụ xây dựng nào.
<script setup>
Việc export thủ công trạng thái và phương thức qua setup()
có thể là công việc đầy đủ thông tin. May mắn thay, nó có thể được tránh khi sử dụng Single-File Components (SFCs). Chúng ta có thể đơn giản hóa việc sử dụng với <script setup>
:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
Biên và hàm được khai báo trong <script setup>
có thể sử dụng tự động trong template của cùngmột component. Hãy xem template như là một hàm JavaScript được khai báo trong cùng một phạm vi - nó tự nhiên có quyền truy cập vào tất cả mọi thứ được khai báo cùng với nó.
TIP
Trong phần hướng dẫn còn lại, chúng tôi chủ yếu sẽ sử dụng cú pháp SFC + <script setup>
cho các ví dụ mã nguồn Composition API, vì đây là cách sử dụng phổ biến nhất cho các nhà phát triển Vue.
Nếu bạn không sử dụng SFC, bạn vẫn có thể sử dụng Composition API với tùy chọn setup()
.
Tại sao lại sử dụng Refs?
Bạn có thể tự hỏi tại sao chúng ta cần sử dụng refs với .value
thay vì các biến đơn. Để giải thích điều này, chúng ta cần thảo luận ngắn gọn về cách hệ thống linh động của Vue hoạt động.
Khi bạn sử dụng một ref trong một template và sau đó thay đổi giá trị của ref, Vue tự động phát hiện sự thay đổi và cập nhật DOM tương ứng. Điều này trở nên có thể nhờ vào hệ thống linh động dựa trên theo dõi phụ thuộc. Khi một component được hiển thị lần đầu tiên, Vue theo dõi mỗi ref đã được sử dụng trong quá trình hiển thị. Sau đó, khi một ref bị biến đổi, nó sẽ kích hoạt một lần hiển thị lại cho các component đang theo dõi nó.
Trong JavaScript tiêu chuẩn, không có cách nào để phát hiện truy cập hoặc biến đổi các biến đơn. Tuy nhiên, chúng ta có thể chặn các hoạt động get và set của các thuộc tính của một đối tượng bằng cách sử dụng các phương thức getter và setter.
Thuộc tính .value
cung cấp cơ hội cho Vue phát hiện khi một ref đã được truy cập hoặc biến đổi. Bên dưới, Vue thực hiện việc theo dõi trong getter của nó và thực hiện việc kích hoạt trong setter của nó. Khái niệm, bạn có thể xem một ref như một đối tượng như sau:
js
// Mã giả, không phải triển khai thực tế
const myRef = {
_value: 0,
get value() {
track() // theo dõi
return this._value
},
set value(newValue) {
this._value = newValue
trigger() // kích hoạt
}
}
Một điều tích cực khác của refs là, khác với các biến đơn, bạn có thể truyền refs vào các hàm và vẫn giữ quyền truy cập vào giá trị mới nhất và kết nối linh động. Điều này đặc biệt hữu ích khi tái cấu trúc logic phức tạp thành mã nguồn có thể tái sử dụng.
Hệ thống linh động được thảo luận chi tiết hơn trong phần Reactivity in Depth.
Deep Reactivity
Refs có thể chứa bất kỳ kiểu giá trị nào, bao gồm các đối tượng lồng nhau sâu, mảng hoặc các cấu trúc dữ liệu tích hợp của JavaScript như Map
.
Một ref sẽ khiến giá trị của nó trở nên reactivity sâu. Điều này có nghĩa là bạn có thể mong đợi rằng các thay đổi sẽ được phát hiện ngay cả khi bạn biến đổi các đối tượng hoặc mảng lồng nhau:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// những thay đổi này sẽ hoạt động như mong đợi.
obj.value.nested.count++
obj.value.arr.push('baz')
}
Giá trị không nguyên thủy được chuyển đổi thành các proxy linh động thông qua reactive()
, được thảo luận bên dưới.
Bạn cũng có thể chọn không sử dụng reactivity sâu với shallow refs. Đối với shallow refs, chỉ có truy cập .value
được theo dõi để linh động. Shallow refs có thể được sử dụng để tối ưu hóa hiệu suất bằng cách tránh chi phí quan sát của các đối tượng lớn, hoặc trong các trường hợp mà trạng thái bên trong được quản lý bởi một thư viện bên ngoài.
Đọc thêm:
Thời Gian Cập Nhật DOM
Khi bạn biến đổi reactive state, DOM sẽ được cập nhật tự động. Tuy nhiên, cần lưu ý rằng các cập nhật DOM không được áp dụng đồng bộ. Thay vào đó, Vue lưu trữ chúng cho đến "tick kế tiếp" trong chu kỳ cập nhật để đảm bảo rằng mỗi component chỉ cần cập nhật một lần dù bạn đã thay đổi trạng thái bao nhiêu lần.
Để đợi cập nhật DOM hoàn tất sau một thay đổi trạng thái, bạn có thể sử dụng nextTick() API toàn cục:
js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// Bây giờ DOM đã được cập nhật
}
reactive()
Có một cách khác để khai báo reactive state, đó là với API reactive()
. Khác với ref, mà bao quanh giá trị bên trong một đối tượng đặc biệt, reactive()
khiến một đối tượng trở nên linh động:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Xem thêm: Gán Kiểu cho Linh Động
Sử dụng trong mẫu:
template
<button @click="state.count++">
{{ state.count }}
</button>
Các đối tượng linh động là Proxy của JavaScript và hoạt động giống như các đối tượng bình thường. Sự khác biệt là Vue có thể chặn truy cập và biến đổi tất cả các thuộc tính của một đối tượng linh động để theo dõi và kích hoạt sự linh động.
reactive()
chuyển đối tượng linh động một cách sâu rộng: các đối tượng lồng nhau cũng được bao quanh bằng reactive()
khi được truy cập. Nó cũng được gọi bên trong ref()
khi giá trị ref là một đối tượng.
Thêm Chi Tiết Về Mở Gói Ref
Như Một Thuộc Tính Của Đối Tượng Linh Động
Một ref được tự động mở gói khi được truy cập hoặc biến đổi như là một thuộc tính của một đối tượng linh động. Nói cách khác, nó hoạt động như một thuộc tính bình thường:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Nếu một ref mới được gán cho một thuộc tính liên kết với một ref đã tồn tại, nó sẽ thay thế ref cũ:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// ref gốc bây giờ không còn liên kết với state.count
console.log(count.value) // 1
Mở gói ref chỉ xảy ra khi nó được lồng sâu bên trong một đối tượng linh động. Nó không áp dụng khi nó được truy cập như là một thuộc tính của một đối tượng linh động mặt.
Lưu Ý Trong Mảng và Bộ Sưu Tập
Không giống với các đối tượng linh động, không có việc mở gói khi ref được truy cập như một phần tử của một mảng linh động hoặc một loại bộ sưu tập nguyên thủy như Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// cần .value ở đây
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// cần .value ở đây
console.log(map.get('count').value)
Lưu Ý Khi Mở Gói Trong Mẫu
Mở gói ref trong template chỉ áp dụng nếu ref là một thuộc tính cấp cao trong ngữ cảnh kết xuất mẫu.
Trong ví dụ dưới đây, count
và object
là các thuộc tính cấp cao, nhưng object.id
không:
js
const count = ref(0)
const object = { id: ref(1) }
Do đó, biểu thức này hoạt động như mong đợi:
template
{{ count + 1 }}
... trong khi biểu thức này KHÔNG:
template
{{ object.id + 1 }}
Kết quả được kết xuất sẽ là [object Object]1
vì object.id
không được mở gói khi đánh giá biểu thức và vẫn là một đối tượng ref. Để sửa điều này, chúng ta có thể phân rã id
thành một thuộc tính cấp cao:
js
const { id } = object
template
{{ id + 1 }}
Bây giờ kết quả kết xuất sẽ là 2
.
Một điều cũng cần lưu ý là một ref sẽ được mở gói nếu nó là giá trị được đánh giá cuối cùng của một nội suy văn bản (ví dụ: một thẻ {{ }}
), vì vậy đoạn mã sau sẽ hiển thị 1
:
template
{{ object.id }}
Điều này chỉ là một tính năng tiện lợi của nội suy văn bản và tương đương với {{ object.id.value }}
.