Chào mừng đến với Cafedev! Trong chủ đề Pinia với Defining a Store, chúng ta sẽ tìm hiểu cách định nghĩa một store trong Pinia. Đây là một bước quan trọng để xây dựng ứng dụng Vue.js của bạn với các khái niệm cốt lõi của Pinia. Chúng ta sẽ khám phá cách sử dụng `defineStore()` để tạo các store và hiểu rõ về các lựa chọn cú pháp cho việc định nghĩa store, bao gồm Option Stores và Setup Stores. Hãy cùng nhau khám phá và nắm bắt kiến thức mới trong bài viết dưới đây!


Trước khi đào sâu vào các khái niệm cốt lõi, chúng ta cần biết rằng một store được xác định bằng cách sử dụng defineStore() và nó yêu cầu một tên duy nhất, được truyền vào như là đối số đầu tiên:

import { defineStore } from 'pinia'

// You can name the return value of `defineStore()` anything you want,
// but it's best to use the name of the store and surround it with `use`
// and `Store` (e.g. `useUserStore`, `useCartStore`, `useProductStore`)
// the first argument is a unique id of the store across your application
export const useAlertsStore = defineStore('alerts', {
  // other options...
})

Tên này, còn được gọi là id, là cần thiết và được sử dụng bởi Pinia để kết nối store với các công cụ phát triển. Việc đặt tên hàm trả về là use… là một quy ước trong các composables để làm cho việc sử dụng của nó trở nên tự nhiên.
defineStore() chấp nhận hai giá trị riêng biệt cho đối số thứ hai của nó: một hàm Setup hoặc một đối tượng Options.

1. Option Stores

Tương tự như Options API của Vue, chúng ta cũng có thể truyền một Đối tượng Options với các thuộc tính state, actions, và getters.

{2-10}
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0, name: 'Eduardo' }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

Bạn có thể coi state như data của store, getters như các thuộc tính computed của store, và actions như các methods.
Option stores nên cảm thấy tự nhiên và đơn giản để bắt đầu sử dụng.

2. Setup Stores

Có một cú pháp khả thi khác để xác định các store. Tương tự như hàm setup của Vue Composition API, chúng ta có thể truyền vào một hàm xác định các thuộc tính và phương thức reactive và trả về một đối tượng với các thuộc tính và phương thức mà chúng ta muốn tiết lộ.

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const name = ref('Eduardo')
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, name, doubleCount, increment }
})

Trong Setup Stores:
ref() trở thành các thuộc tính state
computed() trở thành getters
function() trở thành actions
Lưu ý rằng bạn phải trả về tất cả các thuộc tính state trong các setup stores để Pinia có thể nhận chúng như state. Nói cách khác, bạn không thể có các thuộc tính state riêng tư trong các store. Không trả về tất cả các thuộc tính state có thể làm hỏng SSR, devtools, và các plugin khác.
Setup stores mang lại nhiều linh hoạt hơn so với Option Stores vì bạn có thể tạo watchers bên trong một store và tự do sử dụng bất kỳ composable nào. Tuy nhiên, hãy nhớ rằng việc sử dụng composables sẽ trở nên phức tạp hơn khi sử dụng SSR.

Setup stores cũng có thể phụ thuộc vào các thuộc tính provided toàn cầu như Router hoặc Route. Bất kỳ thuộc tính được cung cấp tại cấp độ App nào đều có thể truy cập từ store bằng cách sử dụng inject(), giống như trong các thành phần:

import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'

export const useSearchFilters = defineStore('search-filters', () => {
  const route = useRoute()
  // this assumes `app.provide('appProvided', 'value')` was called
  const appProvided = inject('appProvided')

  // ...

  return {
    // ...
  }
})

warning Không trả về các thuộc tính như route hoặc appProvided (từ ví dụ ở trên) vì chúng không thuộc về store chính nó và bạn có thể truy cập chúng trực tiếp trong các thành phần bằng useRoute()inject('appProvided').

3. Cú pháp nào tôi nên chọn?

Tương tự như Vue’s Composition API và Options API, hãy chọn cái mà bạn cảm thấy thoải mái nhất. Cả hai đều có ưu và nhược điểm riêng. Option stores dễ làm việc hơn trong khi Setup stores linh hoạt và mạnh mẽ hơn. Nếu bạn muốn đào sâu hơn vào sự khác biệt, hãy kiểm tra Option Stores vs Setup Stores chapter trong Mastering Pinia.

4. Sử dụng store

<script setup>
import { useCounterStore } from '@/stores/counter'

// access the `store` variable anywhere in the component ✨
const store = useCounterStore()
</script>

tip Nếu bạn chưa sử dụng các thành phần setup , bạn vẫn có thể sử dụng Pinia với các _map helpers_.
Bạn có thể xác định bất kỳ số store nào bạn muốn và bạn nên xác định mỗi store trong một file khác nhau để tận dụng tối đa Pinia (như cho phép tự động phân chia mã của bạn và cung cấp suy luận TypeScript).

Khi store được khởi tạo, bạn có thể truy cập bất kỳ thuộc tính nào được xác định trong state, getters, và actions trực tiếp trên store. Chúng tôi sẽ xem xét chi tiết các điều này trong các trang tiếp theo nhưng tính tự động hoàn thành sẽ giúp bạn.

Lưu ý rằng store là một đối tượng được bao bọc bằng reactive, có nghĩa là không cần viết .value sau getters nhưng, giống như props trong setup, chúng ta không thể phân rã nó:

<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'

const store = useCounterStore()
// ❌ This won't work because it breaks reactivity
// it's the same as destructuring from `props`
const { name, doubleCount } = store // [!code warning]
name // will always be "Eduardo" // [!code warning]
doubleCount // will always be 0 // [!code warning]

setTimeout(() => {
  store.increment()
}, 1000)

// ✅ this one will be reactive
// 💡 but you could also just use `store.doubleCount` directly
const doubleValue = computed(() => store.doubleCount)
</script>

5. Phân rã từ một Store

Để trích xuất các thuộc tính từ store trong khi giữ nguyên tính phản ứng của nó, bạn cần sử dụng storeToRefs(). Nó sẽ tạo ra các refs cho mọi thuộc tính phản ứng. Điều này hữu ích khi bạn chỉ sử dụng state từ store mà không gọi bất kỳ hành động nào. Lưu ý bạn có thể phân rã hành động trực tiếp từ store vì chúng được liên kết với store luôn:

<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const store = useCounterStore()
// `name` and `doubleCount` are reactive refs
// This will also extract refs for properties added by plugins
// but skip any action or non reactive (non ref/reactive) property
const { name, doubleCount } = storeToRefs(store)
// the increment action can just be destructured
const { increment } = store
</script>

Như vậy là chúng ta đã kết thúc bài viết về Pinia Vuejs với Defining a Store trên Cafedev. Hy vọng bạn đã học được nhiều điều mới và có thêm kiến thức về cách định nghĩa một store trong Pinia. Bằng cách hiểu rõ về các cú pháp và khái niệm cơ bản của Pinia, bạn có thể xây dựng ứng dụng Vue.js của mình một cách linh hoạt và mạnh mẽ hơn. Hãy tiếp tục khám phá thêm về Vue.js và các công nghệ liên quan trên Cafedev!

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!