Appearance
State Management
Quản lý Trạng Thái là Gì?
Kỹ thuật, mọi trường môi trường Vue component đã "quản lý" trạng thái phản ứng của nó. Hãy xem xét một ví dụ về một component đơn giản tính toán:
vue
<script setup>
import { ref } from 'vue'
// trạng thái
const count = ref(0)
// hành động
function increment() {
count.value++
}
</script>
<!-- giao diện -->
<template>{{ count }}</template>
Nó là một đơn vị tự chứa với các phần sau:
- Trạng thái, nguồn thông tin chính của ứng dụng chúng ta;
- Giao diện, một ánh xạ tuyên bố của trạng thái;
- Hành động, các cách có thể trạng thái có thể thay đổi đáp ứng vào các đầu vào từ giao diện.
Đây là một biểu diễn đơn giản của khái niệm "luồng dữ liệu một chiều":
Tuy nhiên, đơn giản này bắt đầu phá vỡ khi chúng ta có nhiều component chia sẻ cùng một trạng thái:
- Nhiều giao diện có thể phụ thuộc vào cùng một phần của trạng thái.
- Hành động từ các giao diện khác nhau có thể cần biến đổi cùng một phần của trạng thái.
Đối với trường hợp một, một giải pháp có thể là "đẩy" trạng thái được chia sẻ lên một component tổ tiên chung, sau đó chuyển nó xuống như là các props. Tuy nhiên, điều này nhanh chóng trở nên phiền toái trong cây component có cấu trúc sâu, dẫn đến một vấn đề khác được biết đến là Prop Drilling.
Đối với trường hợp hai, chúng ta thường thấy mình phải dựa vào các giải pháp như trực tiếp truy cập các thể hiện cha/con thông qua các refs template hoặc cố gắng biến đổi và đồng bộ hóa nhiều bản sao của trạng thái thông qua sự kiện phát ra. Cả hai mô hình này đều mong manh và dẫn nhanh chóng đến mã nguồn code không bảo trì được.
Một giải pháp đơn giản và trực tiếp hơn là trích xuất trạng thái được chia sẻ khỏi các component và quản lý nó trong một singleton toàn cầu. Với điều này, cây component của chúng ta trở thành một "giao diện" lớn, và bất kỳ component nào cũng có thể truy cập trạng thái hoặc kích hoạt hành động, bất kỳ nơi nào chúng ta đặt chúng trong cây!
Quản lý Trạng Thái Đơn Giản với Reactivity API
Nếu bạn có một phần của trạng thái cần được chia sẻ bởi nhiều thể hiện, bạn có thể sử dụng reactive()
để tạo một đối tượng phản ứng, sau đó nhập nó vào nhiều component:
js
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0
})
vue
<!-- ComponentA.vue -->
<script setup>
import { store } from './store.js'
</script>
<template>Từ A: {{ store.count }}</template>
vue
<!-- ComponentB.vue -->
<script setup>
import { store } from './store.js'
</script>
<template>Từ B: {{ store.count }}</template>
Bây giờ mỗi khi đối tượng store
bị biến đổi, cả <ComponentA>
và <ComponentB>
sẽ tự động cập nhật giao diện của họ - chúng ta có một nguồn thông tin duy nhất ngay bây giờ.
Tuy nhiên, điều này cũng có nghĩa là bất kỳ component nào nhập store
đều có thể biến đổi nó bất cứ cách nào họ muốn:
template
<template>
<button @click="store.count++">
Từ B: {{ store.count }}
</button>
</template>
Mặc dù điều này hoạt động trong các trường hợp đơn giản, nhưng trạng thái toàn cầu có thể bị biến đổi một cách tùy ý bởi bất kỳ component nào sẽ không bền lâu trong tương lai. Để đảm bảo logic biến đổi trạng thái được tập trung giống như trạng thái, khuyến nghị đặt các phương thức trên store với tên thể hiện ý định của hành động:
js
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0,
increment() {
this.count++
}
})
template
<template>
<button @click="store.increment()">
Từ B: {{ store.count }}
</button>
</template>
TIP
Lưu ý rằng bộ xử lý click sử dụng store.increment()
với dấu ngoặc - điều này là cần thiết để gọi phương thức với ngữ c
ảnh this
đúng vì nó không phải là một phương thức component.
Mặc dù ở đây chúng ta đang sử dụng một đối tượng phản ứng đơn lẻ như một store, bạn cũng có thể chia sẻ trạng thái phản ứng được tạo bằng cách sử dụng các API Phản Ứng khác như ref()
hoặc computed()
, hoặc thậm chí trả về trạng thái toàn cầu từ một Composable:
js
import { ref } from 'vue'
// trạng thái toàn cầu, được tạo trong phạm vi module
const globalCount = ref(1)
export function useCount() {
// trạng thái cục bộ, được tạo cho mỗi component
const localCount = ref(1)
return {
globalCount,
localCount
}
}
Sự phân biệt giữa hệ thống phản ứng Vue và mô hình component làm cho nó cực kỳ linh hoạt.
Xem xét về SSR
Nếu bạn đang xây dựng một ứng dụng tận dụng Rendering Phía Server (SSR), mô hình trên có thể dẫn đến vấn đề do store là một singleton được chia sẻ qua nhiều yêu cầu. Điều này được thảo luận chi tiết hơn trong hướng dẫn SSR.
Pinia
Trong khi giải pháp quản lý trạng thái tự chế của chúng ta có thể đủ cho các tình huống đơn giản, có nhiều điều cần xem xét hơn trong các ứng dụng sản xuất lớn:
- Quy ước mạnh mẽ cho sự hợp tác nhóm
- Tích hợp với Vue DevTools, bao gồm dòng thời gian, kiểm tra trong component, và gỡ lỗi thời gian di chuyển
- Hot Module Replacement
- Hỗ trợ Rendering Phía Server
Pinia là một thư viện quản lý trạng thái thực hiện tất cả các điều trên. Nó được duy trì bởi nhóm lõi Vue, và hoạt động với cả Vue 2 và Vue 3.
Người dùng hiện tại có thể quen thuộc với Vuex, thư viện quản lý trạng thái trước đó được duy trì chính thức cho Vue. Với Pinia đảm nhận vai trò tương tự trong hệ sinh thái, Vuex hiện đang ở chế độ bảo trì. Nó vẫn hoạt động, nhưng sẽ không nhận thêm tính năng mới. Được khuyến nghị sử dụng Pinia cho các ứng dụng mới.
Pinia bắt đầu như là một khám phá về cái gì phiên bản tiếp theo của Vuex có thể trông như thế nào, tích hợp nhiều ý tưởng từ các cuộc thảo luận của nhóm lõi về Vuex 5. Cuối cùng, chúng tôi nhận ra rằng Pinia đã thực hiện hầu hết những gì chúng tôi muốn trong Vuex 5, và quyết định làm nó làm đề xuất mới thay thế.
So với Vuex, Pinia cung cấp một API đơn giản hơn với ít lễ nghi hơn, cung cấp API theo kiểu Composition-API, và quan trọng nhất, hỗ trợ suất rất tốt khi sử dụng với TypeScript.