Chào mừng bạn đến với Cafedev – nơi chia sẻ kiến thức và kinh nghiệm về công nghệ! Hôm nay, chúng ta sẽ cùng nhau khám phá chủ đề Vuejs with State Management – một chủ đề hấp dẫn và cần thiết cho các nhà phát triển Vue. Trong thế giới phát triển web hiện đại, quản lý trạng thái là một phần không thể thiếu của mọi ứng dụng. Với Vuejs, chúng ta có nhiều cách tiếp cận để quản lý trạng thái của ứng dụng một cách hiệu quả và linh hoạt. Hãy cùng bắt đầu khám phá và học hỏi nhé!

1. Quản lý Trạng thái là gì?

Kỹ thuật, mỗi thể hiện của thành phần Vue đã “quản lý” trạng thái phản ứng của riêng nó. Hãy xem một ví dụ về thành phần đếm đơn giản:

Ví dụ theo 2 kiểu options và composition

<script setup>
import { ref } from 'vue'

// state
const count = ref(0)

// actions
function increment() {
  count.value++
}
</script>

<!-- view -->
<script>
export default {
  // state
  data() {
    return {
      count: 0
    }
  },
  // actions
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<!-- view -->

Đây 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 mà điều khiển ứng dụng của chúng ta;
Giao diện, một ánh xạ phổ cập của trạng thái;
Các hành động, các cách có thể thay đổi trạng thái phản ứng với đầu vào của người dùng 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, sự đơn giản bắt đầu phá vỡ khi chúng ta có **nhiều thành phần chia sẻ cùng một trạng thái**:
1. Nhiều giao diện có thể phụ thuộc vào cùng một phần của trạng thái.

2. Các hành động từ các giao diện khác nhau có thể cần thay đổi cùng một phần của trạng thái.

Đối với trường hợp một, một phương án có thể là “nâng” trạng thái chia sẻ lên thành một thành phần tổ tiên chung, và sau đó chuyển nó xuống dưới dưới dạng props. Tuy nhiên, điều này nhanh chóng trở nên nhàm chán trong cây thành phần có cấu trúc phân cấp 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 tìm cách như truy cập trực tiếp vào các thể hiện cha / con thông qua các tham chiếu mẫu, hoặc cố gắng thay đổi và đồng bộ hóa nhiều bản sao của trạng thái thông qua các sự kiện phát ra. Cả hai mẫu mẽ này đều dễ gãy và nhanh chóng dẫn đến mã không thể 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 chia sẻ ra khỏi các thành phần và quản lý nó trong một đối tượng duy nhất toàn cầu. Với điều này, cây thành phần của chúng ta trở thành một “giao diện” lớn, và bất kỳ thành phần nào cũng có thể truy cập vào trạng thái hoặc kích hoạt hành động, bất kể chúng ở đâu trong cây!

2. Quản lý Trạng thái Đơn giản với API Phản ứng

Trong API Tùy chọn, dữ liệu phản ứng được khai báo bằng cách sử dụng tùy chọn `data()`. Bên trong, đối tượng được trả về bởi `data()` được làm phản ứng thông qua hàm reactive(), cũng có sẵn như một API công cộng.
Nếu bạn có một phần của trạng thái mà nê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, và sau đó nhập nó vào nhiều thành phần:

Dưới đây là ví dụ theo 2 kiểu options và composition

// store.js
import { reactive } from 'vue'

export const store = reactive({
  count: 0
})
<!-- ComponentA.vue -->
<script setup>
import { store } from './store.js'
</script>

<template>From A: {{ store.count }}</template>
<!-- ComponentB.vue -->
<script setup>
import { store } from './store.js'
</script>

<template>From B: {{ store.count }}</template>
<!-- ComponentA.vue -->
<script>
import { store } from './store.js'

export default {
  data() {
    return {
      store
    }
  }
}
</script>
<!-- ComponentB.vue -->
<script>
import { store } from './store.js'

export default {
  data() {
    return {
      store
    }
  }
}
</script>

Bây giờ mỗi khi đối tượng `store` được biến đổi, cả <ComponentA> và <ComponentB> sẽ tự động cập nhật giao diện của mình – bây giờ chúng ta có một nguồn thông tin duy nhất.
Tuy nhiên, điều này cũng có nghĩa là bất kỳ thành phần nào nhập `store` đều có thể biến đổi nó theo bất kỳ cách nào:

<template>
  <button @click="store.count++">
    From 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ể được biến đổi ngẫu nhiên bởi bất kỳ thành phần nào không sẽ không được bảo trì lâu dài. Để đảm bảo logic biến đổi trạng thái được tập trung như trạng thái chính nó, nên định nghĩa các phương thức trên store với tên biểu thị ý định của các hành động:


// store.js
import { reactive } from 'vue'

export const store = reactive({
  count: 0,
  increment() {
    this.count++
  }
})
<template>
  <button @click="store.increment()">
    From B: {{ store.count }}
  </button>
</template>

Thử nghiệm ở Play

Gợi ý Lưu ý rằng trình xử lý click sử dụng `store.increment()` với dấu ngoặc đơn – đ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 thành phần.

Mặc dù ở đây chúng ta đang sử dụng một đối tượng phản ứng duy nhất như một cửa hàng, bạn cũng có thể chia sẻ trạng thái phản ứng được tạo bằng các Reactivity APIs 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:

import { ref } from 'vue'

// global state, created in module scope
const globalCount = ref(1)

export function useCount() {
  // local state, created per-component
  const localCount = ref(1)

  return {
    globalCount,
    localCount
  }
}

Sự thật rằng hệ thống phản ứng của Vue được tách biệt khỏi mô hình thành phần làm cho nó cực kỳ linh hoạt.

3. Xem xét SSR

Nếu bạn đang xây dựng một ứng dụng sử dụng , mẫu trên có thể dẫn đến vấn đề do store là một đối tượng duy nhất được chia sẻ trên nhiều yêu cầu. Điều này được thảo luận trong thêm chi tiết trong hướng dẫn SSR.

4. Pinia

Mặc dù giải pháp quản lý trạng thái tự tạo của chúng tôi sẽ đủ trong các tình huống đơn giản, nhưng còn nhiều vấn đề khác cần xem xét trong các ứng dụng sản xuất quy mô lớn:
– Quy ước mạnh mẽ cho việc 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 thành phần và gỡ lỗi trong thời gian chạy
– Thay Thế Mô-đun Nóng
– Hỗ trợ Rendering Trên Máy Chủ

Pinia là một thư viện quản lý trạng thái thực hiện tất cả những điều trên. Nó được bảo 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 chính thức trướ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 được tính năng mới nào nữa. Đề nghị sử dụng Pinia cho các ứng dụng mới.

Pinia bắt đầu như là một sự khám phá về cái gì sẽ là 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 cho 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à đề 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 thủ tục hơn, cung cấp các API theo phong cách Composition-API, và quan trọng nhất, có hỗ trợ suy luận kiểu mạnh mẽ khi sử dụng với TypeScript.

Chúng ta đã khám phá sâu hơn về Vuejs with State Management thông qua bài viết này trên Cafedev. Hy vọng rằng bạn đã có cái nhìn tổng quan và kiến thức cơ bản về cách Vuejs có thể được sử dụng để quản lý trạng thái trong ứng dụng web của bạn. Nếu bạn muốn tìm hiểu thêm về các chủ đề liên quan đến công nghệ và phát triển phần mềm, đừng quên ghé thăm Cafedev thường xuyên để cập nhật thông tin mới nhất và chia sẻ kiến thức cùng cộng đồng!

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!