Bài 5: State Management với Pinia trong Vue.js 3

Bài 5: State Management với Pinia trong Vue.js 3

Khi ứng dụng còn nhỏ, việc truyền dữ liệu qua props hay emit event giữa các component là đủ. Nhưng với ứng dụng lớn, dữ liệu phân tán khắp nơi → rất dễ rối. Lúc này ta cần một state management (quản lý dữ liệu tập trung).

Với Vue 2 thì thường dùng Vuex, còn Vue 3 thì Pinia là lựa chọn được khuyên dùng vì đơn giản và hiện đại hơn.

1. Cài đặt Pinia

Trong project Vue 3 (Vite), cài pinia:

npm install pinia

Trong main.js đăng ký Pinia vào Vue app:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia()) // đăng ký Pinia
app.mount('#app')

2. Tạo Store đầu tiên

Trong thư mục src/stores/, tạo file counter.js:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),

  getters: {
    double: (state) => state.count * 2, // giống computed
  },

  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
  },
})

3. Sử dụng Store trong Component

Ví dụ trong App.vue:

<template>
  <div class="app">
    <h1>📊 Pinia Counter Example</h1>
    <p>Count: {{ counter.count }}</p>
    <p>Double: {{ counter.double }}</p>

    <button @click="counter.increment">➕ Tăng</button>
    <button @click="counter.decrement">➖ Giảm</button>
  </div>
</template>

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

const counter = useCounterStore()
</script>

<style>
.app {
  font-family: sans-serif;
  text-align: center;
  margin-top: 50px;
}
button {
  margin: 5px;
  padding: 8px 16px;
}
</style>

👉 Ở đây counter chính là store, có thể truy cập state (count), getter (double), và actions (increment, decrement).

4. Modular Store

Khi app lớn, ta có thể chia store ra thành nhiều module.

Ví dụ:

  • stores/counter.js → quản lý bộ đếm

  • stores/user.js → quản lý thông tin user

user.js:

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'Thanh Nguyen',
    loggedIn: false,
  }),
  actions: {
    login(name) {
      this.name = name
      this.loggedIn = true
    },
    logout() {
      this.name = ''
      this.loggedIn = false
    },
  },
})

Trong App.vue, dùng cả 2 store:

<template>
  <div>
    <h2>User: {{ user.loggedIn ? user.name : 'Guest' }}</h2>
    <button v-if="!user.loggedIn" @click="user.login('Nguyễn Bá Thành')">Login</button>
    <button v-else @click="user.logout">Logout</button>
  </div>
</template>

<script setup>
import { useUserStore } from './stores/user'
const user = useUserStore()
</script>

5. Persist State (lưu vào localStorage)

Mặc định khi reload trang, store sẽ reset. Để giữ lại, ta có thể dùng plugin pinia-plugin-persistedstate:

npm install pinia-plugin-persistedstate

Trong main.js:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

const app = createApp(App)
app.use(pinia)
app.mount('#app')

Trong user.js, bật persist:

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    loggedIn: false,
  }),
  actions: {
    login(name) {
      this.name = name
      this.loggedIn = true
    },
    logout() {
      this.name = ''
      this.loggedIn = false
    },
  },
  persist: true, // dữ liệu lưu vào localStorage
})

👉 Giờ thì sau khi login, reload trang vẫn giữ được trạng thái.


🎯 Tổng kết

  • Pinia là state management chính thức cho Vue 3, thay thế Vuex.

  • Cấu trúc store có state, getter, actions.

  • Có thể chia module cho app lớn.

  • Dùng plugin để persist state vào localStorage hoặc sessionStorage.