Pinia – Quản lý state trong Vue nhỏ gọn, dễ dùng hơn Vuex rất nhiều

2870

Bài viết được sự cho phép bởi tác giả Sơn Dương

Hiện nay, Vuex vẫn là thư viện phổ biến nhất, được sử dụng trong hầu hết các dự án. Tuy nhiên, khi Pinia ra đời và được team phát triển Vue khuyến khích sử dụng đã thay đổi cuộc chơi.

Pinia thừa hưởng toàn bộ ưu điểm của Vuex và bổ sung nhiều tính năng hấp dẫn, nhưng vẫn giữ được tính gọn nhẹ và dễ sử dụng. Pinia thực sự là một ứng cử viên sáng giá để thay thế vuex.

Chúng ta cùng nhau tìm hiểu cách sử dụng Pinia này nhé!

Pinia là gì?

Pinia là thư viện quản lý state trong Vue (State Management), phục vụ cho việc lưu trữ và chia sẻ state giữa các component với nhau.

Về cơ bản thì Pinia tương tự với Vuex. Vì Pinia ra đời sau Vuex, nên Pinia thừa hưởng những thế mạnh của Vuex. Tất nhiên, Pinia tương thích hoàn toàn với cả hai phiên bản Vue 2.x và Vue 3.

Cấu trúc và cú pháp của Pinia cũng rất quen thuộc, cũng có action, store, getter… Nhưng các cài đặt và cấu hình store cho Pinia lại đơn giản đến bất ngờ.

Pinia – Quản lý state trong Vue

Với xu hướng được sử dụng ngày càng nhiều, Pinia có lẽ sẽ sớm thay thế Vuex trong tương lai gần thôi.

  3 cách truyền dữ liệu giữa các Components trong Vue.js

  Giới thiệu cấu trúc dự án tạo bằng Vuejs CLI

Tại sao chọn Pinia thay thế Vuex

Trong số các ưu điểm của thư viện Pinia, cá nhân mình thấy nó nổi trổi nhất ở các điểm sau:

  • Kích thước thư viện siêu nhẹ, chỉ khoảng 1kb
  • Hỗ trợ module hóa tốt
  • Hỗ trợ auto complete khi code tốt. Thay vì phải khai báo mapAction(…), mapGetter(…).v.v…
  • Dễ sử dụng, vì cách viết giống như bạn vẫn khai báo và sử dụng state trong từng component vậy.

Và còn nhiều ưu điểm khác nữa. Mình nghĩ sau khi thực hành và ứng dụng vào dự án, bạn sẽ trải nghiệm và nhận ra các ưu điểm này.

Xem thêm nhiều tuyển dụng VueJS hấp dẫn trên TopDev

Thực hành quản lý state sử dụng Pinia

Trước hết, chúng ta tạo mới một dự án Vue để thực hành đã nhé.

Vẫn câu lệnh quen thuộc:

vue create hello-pinia

(Dạo này đứt cáp quang biển nên việc tải thư viện, cài đặt dependencies lâu quá!)

Sau khi tạo xong dự án, bạn chạy thử dự án:

npm run serve

Vào trình duyệt để kiểm tra kết quả: http://localhost:8080

Trong bài viết này, mình sẽ sử dụng phiên bản Vue 3 cho nó hiện đại

Cài đặt và cấu hình Store với Pinia

Cài đặt Pinia cũng tương tự như các thư viện khác thôi. Chúng ta sẽ cài đặt qua npm bằng câu lệnh:

npm install pinia

Sau khi cài xong, quay trở lại main.js để thêm pinia vào Vue.

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

createApp(App).use(createPinia()).mount('#app')

Bước tiếp theo là cấu hình store với Pinia.

Pinia có cách  tiếp cận hơi khác một chút so với Vuex. Đó là Pinia xử lý state global theo hướng module. Tức là nó sẽ tạo nhiều store nhỏ lẻ tương ứng với một key duy nhất. Sau đó, Pinia sẽ kết hợp chúng lại thành một store duy nhất cho toàn ứng dụng.

Một store có 4 thuộc tính:

  • ID: là khóa duy nhất như đã đề cập ở trên để xác định store
  • state: là một hàm trả về trạng thái dữ liệu ban đầu của state
  • getters: là nơi định nghĩa các giá trị cần lấy từ store
  • actions: là nơi định nghĩa các hành động cập nhật state. Nó là sự kết hợp của action + mutation so với Vuex.

Ví dụ một đoạn code định nghĩa store bằng Pinia:

import { defineStore } from "pinia";

export const useTodoStore = defineStore({
  id: "uniqueID",
  state: () => ({
    // ...
  }),
  getters: {
    // ...
  },
  actions: {
    // ...
  },
});

Thực hành dự án với Pinia

Quay trở lại với dự án mà chúng ta đã tạo trước đó. Với dự án này, chúng ta sẽ xây dựng một ứng dụng quản lý công việc siêu đơn giản.

Có 2 tính năng chính:

  • Tạo mới công việc.
  • Hiển thị danh sách công việc.
  • Xóa một công việc

Giao diện ứng dụng khi hoàn thành sẽ như sau:

Pinia – Quản lý state trong Vue

Đầu tiên, chúng ta sẽ cài đặt store với 2 actions cơ bản là: addTodo, removeTodo.

// stores/todo.ts
import { defineStore } from "pinia";

export const useTodoStore = defineStore({
  id: "todoState",
  state: () => ({
    todos: [],
  }),
  getters: {
    totalTodos: (state) => state.todos.length,
  },
 actions: {
   addTodo(title, description) {
     const todo = {
       id: Math.floor(Math.random() * 10000), // random ID
       title,
       description,
    };
    this.todos = [todo, ...this.todos];
  },

  async removeTodo(id) {
    // remove todos
    this.todos = this.todos.filter((todo) => todo.id !== id);
  },
 },
});

Về phần giao diện, mình chia nhỏ giao diện thành 2 components chính: Phần nhập công việc mới và phần danh sách các công việc.

Trong phần này, các bạn chú ý tới hàm setup(). Đây là nơi mình khai báo tham chiếu tới store: const storeTodo = useTodoStore() nhờ đó mà mình có thể gọi tới actions và truy xuất giá trị của global state.

<!-- components/TodoForm.vue -->
<template>
<form @submit="onSubmit">
<h2>VNTALKING - Quản lý công việc</h2>

<!-- title -->
<div class="field">
<label class="label">Tiêu đề</label>
<input type="text" class="input" name="title" v-model="title" />
</div>

<!-- description -->
<div class="field">
<label class="label">Miêu tả</label>
<textarea class="input" name="description" v-model="description"></textarea>
</div>

<!-- submit -->
<div class="field">
<button type="submit">Tạo mới công việc</button>
</div>
</form>
</template>

<script>
import { defineComponent } from "vue";
import { useTodoStore } from "../stores/todo";

export default defineComponent({
 name: "TodoForm",
 data() {
return {
 title: "",
 description: "",
};
},
 setup() {
const storeTodo = useTodoStore();
return { storeTodo };
},
 methods: {
 onSubmit(e) {
 e.preventDefault();

if (!this.title) {
return;
}

 // save data into store
this.storeTodo.addTodo(this.title, this.description);

 // clear data
this.title = "";
this.description = "";
},
},
});
</script>

Và danh sách công việc:

<!-- components/TodoList.vue -->
<template>
  <div>
    <div v-for="todo of storeTodo.todos" :key="todo.id" class="wrapper">
      <div class="header">
        <div class="title">[{{ todo.id }}] {{ todo.title }}</div>
        <div>
          <button type="button" @click="storeTodo.removeTodo(todo.id)">Xóa</butto</div>
        </div>
      <div v-if="todo?.description">{{ todo?.description }}</div>
</div>
    </div>
</template>
<script>
import { defineComponent } from "vue";
import { useTodoStore } from "../stores/todo";
export default defineComponent({
  name: "TodoList",
  setup() {
    const storeTodo = useTodoStore();
    return { storeTodo };
  },
});
</script>

Như bạn cũng thấy, với cách viết của Pinia, mình không cần phải sử dụng mapActions(...) hay mapGetters(...) mà VS Code vẫn tự động gợi ý lệnh. Đây là điều mà mình cảm thấy hứng thú nhất với Pinia so với Vuex!

Các bạn có thể tải mã nguồn đầy đủ về tham khảo:

Tạm kết

Sau khi trải nghiệm quản lý state với Pinia xong, cám giác của bạn thế nào? Có thấy Pinia xứng đáng để thay thế Vuex ở thời điểm hiện tại này không?

Có lẽ không phải ngẫu nhiên mà team phát triển Vue lại khuyến khích sử dụng Pinia. Chắc chắn họ cũng nhận ra những ưu điểm và cũng có chiến lược phát triển Vue tương đồng với Pinia.

Bài viết gốc được đăng tải tại vntalking.com

Xem thêm:

Xem thêm các việc làm IT hấp dẫn trên TopDev