Bài 6: Làm việc với API trong Vue.js 3

Bài 6: Làm việc với API trong Vue.js 3

Một ứng dụng thực tế không thể thiếu việc giao tiếp với backend. Trong Vue.js 3, chúng ta có thể dùng Fetch API (native) hoặc Axios (thư viện phổ biến).

Trong bài này, mình sẽ hướng dẫn:

  1. Gọi API với Axios

  2. Quản lý Loading & Error

  3. Tổ chức API service riêng

  4. Tích hợp với Pinia (lưu dữ liệu vào store)

  5. Thêm Pagination & Infinite Scroll

1. Cài Axios

npm install axios

Tạo file src/api/axios.js để cấu hình:

import axios from 'axios'

const api = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com', // API giả lập
  timeout: 5000,
})

export default api

2. Gọi API trong Component (cơ bản)

Ví dụ trong App.vue gọi API lấy danh sách posts:

<template>
  <div class="app">
    <h1>📡 API Example</h1>

    <button @click="fetchPosts">Load Posts</button>

    <p v-if="loading">Loading...</p>
    <p v-if="error" style="color:red">{{ error }}</p>

    <ul>
      <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import api from './api/axios'

const posts = ref([])
const loading = ref(false)
const error = ref(null)

async function fetchPosts() {
  loading.value = true
  error.value = null
  try {
    const res = await api.get('/posts?_limit=5')
    posts.value = res.data
  } catch (err) {
    error.value = 'Lỗi khi gọi API'
  } finally {
    loading.value = false
  }
}
</script>

👉 Đây là cách cơ bản: có loading, error, và hiển thị danh sách.

3. Tổ chức API service riêng

Thay vì gọi trực tiếp trong component, ta nên tách ra service.

Tạo src/api/postService.js:

import api from './axios'

export const postService = {
  getPosts(limit = 5, page = 1) {
    return api.get(`/posts?_limit=${limit}&_page=${page}`)
  },
  getPostById(id) {
    return api.get(`/posts/${id}`)
  },
}

4. Tích hợp với Pinia

Tạo store src/stores/post.js:

import { defineStore } from 'pinia'
import { postService } from '../api/postService'

export const usePostStore = defineStore('post', {
  state: () => ({
    posts: [],
    loading: false,
    error: null,
    page: 1,
  }),
  actions: {
    async fetchPosts(limit = 5) {
      this.loading = true
      this.error = null
      try {
        const res = await postService.getPosts(limit, this.page)
        this.posts = res.data
      } catch (err) {
        this.error = 'Lỗi khi tải posts'
      } finally {
        this.loading = false
      }
    },
    async nextPage(limit = 5) {
      this.page++
      await this.fetchPosts(limit)
    },
  },
})

Trong App.vue:

<template>
  <div>
    <h1>📑 Post List</h1>
    <button @click="postStore.fetchPosts()">Load Posts</button>

    <p v-if="postStore.loading">Loading...</p>
    <p v-if="postStore.error" style="color:red">{{ postStore.error }}</p>

    <ul>
      <li v-for="post in postStore.posts" :key="post.id">{{ post.title }}</li>
    </ul>

    <button @click="postStore.nextPage()" :disabled="postStore.loading">
      Next Page
    </button>
  </div>
</template>

<script setup>
import { usePostStore } from './stores/post'

const postStore = usePostStore()
</script>

5. Infinite Scroll (vô tận)

Thêm vào store một hàm loadMore:

async loadMore(limit = 5) {
  this.page++
  this.loading = true
  try {
    const res = await postService.getPosts(limit, this.page)
    this.posts = [...this.posts, ...res.data] // nối thêm vào list
  } catch (err) {
    this.error = 'Lỗi khi tải thêm posts'
  } finally {
    this.loading = false
  }
}

Trong component, dùng @scroll hoặc thư viện như vueuse để bắt sự kiện khi kéo gần cuối trang rồi gọi postStore.loadMore().


🎯 Tổng kết

  • Dùng Axios để gọi API, quản lý loading, error.

  • Nên tách API thành service để dễ quản lý.

  • Tích hợp Pinia để lưu dữ liệu tập trung.

  • Hỗ trợ phân trang và infinite scroll.