Zum Inhalt springen
DasPeTe Tutorials

Einfaches API-Fetch-Composable

So erstellst du ein einfaches API-Fetch-Composable in Vue.js

Fortgeschritten40 Min.

Bei der Arbeit mit APIs in Vue.js ist es üblich, ein Composable für die API-Anfragen zu erstellen. So kannst du dieselbe Logik in verschiedenen Komponenten wiederverwenden. Gehen wir das Schritt für Schritt durch. Als Beispiel-API verwenden wir die Faker API (https://fakerapi.it).

Das Composable useApi

Zuerst erstellen wir eine neue Datei namens useApi.ts im Ordner composables. Darin bauen wir mit der Fetch API ein einfaches API-Fetch-Composable.

composables/useApi.ts
export const useApi = (apiUrl: string) => {
  // create default request options for the default headers, or credentials if needed
  const defaultRequestOptions: Partial<RequestInit> = {
    // credentials: 'include',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
  };

  // utility function to get headers, can be extended to add auth tokens etc.
  const getHeaders = () => {
    const headers: Record<string, string> = {
      ...(defaultRequestOptions.headers as Record<string, string>),
    };

    // here you can add more headers if needed, e.g., authentication tokens

    return headers;
  };

  // main function to perform the fetch request
  const fetchFromApi = async (method: string, endpoint: string, data?: unknown) => {
    const response = await fetch(`${apiUrl}${endpoint}`, {
      ...defaultRequestOptions,
      method,
      headers: getHeaders(),
      body: data ? JSON.stringify(data) : undefined,
    });

    if (!response.ok) {
      try {
        // try to get error message from response
        // here you can customize error handling as needed
        const error = await response.json();
        throw new Error(error.message || 'API request failed');
      } catch (error) {
        throw error;
      }
    }

    return response.json();
  };

  // helper methods for different HTTP verbs
  const get = async (endpoint: string) => fetchFromApi('GET', endpoint);
  const post = async (endpoint: string, data: unknown) => fetchFromApi('POST', endpoint, data);
  const put = async (endpoint: string, data: unknown) => fetchFromApi('PUT', endpoint, data);
  const del = async (endpoint: string) => fetchFromApi('DELETE', endpoint);

  return {
    get,
    post,
    put,
    del,
  };
};

Verwendung des einfachen Composables useApi

Jetzt, wo wir unser Composable useApi haben, können wir es in unseren Vue-Komponenten verwenden.

src/components/UserList.vue
<template>
  <div>
    User List Component

    <ul>
      <li
        v-for="user in users"
        :key="user.id"
      >
        {{ user.firstname }} {{ user.lastname }} - {{ user.email }}
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref, type Ref } from 'vue';
import { useApi } from '../composables/useApi';

// initialize the useApi composable with the base URL of the Faker API
const { get } = useApi('https://fakerapi.it/api/v2/');

// define a User type for better type checking
type User = {
  id: number;
  firstname: string;
  lastname: string;
  email: string;
};
// reactive variable to hold the list of users
const users: Ref<User[]> = ref([]);

onMounted(async () => {
  // fetch users from the API and assign to the users variable
  users.value = (await get('users')).data;
});
</script>

In diesem Beispiel haben wir eine Komponente UserList erstellt, die mit unserem Composable useApi eine Liste von Benutzern von der Faker API abruft. Beim Einhängen der Komponente ruft sie die Methode get auf, lädt die Benutzer und zeigt sie in einer Liste an.

Wie du siehst, fehlen hier aber noch einige Dinge, zum Beispiel der Ladezustand oder die Fehlerbehandlung. Vielleicht möchtest du auch ein spezifischeres Composable für verschiedene Entitäten wie Benutzer, Beiträge und so weiter haben. Verbessern wir unser Composable, damit es diese Fälle abdeckt.

Generisches Entity-Composable

Da wir nun ein generisches Composable useApi haben, können wir darauf aufbauend ein generisches Composable useEntity erstellen.

Zuerst erstellen wir eine neue Datei namens useEntity.ts im Ordner composables.

composables/useEntity.ts
import { ref } from 'vue';
import { useApi } from './useApi';

export type EntitySettings = {
  // the API endpoint for the entity
  endpoint: string;
  // the key of the response array data
  listDataKey: string;
};

// Options for fetching list data
// These can be extended based on API capabilities
export type FetchListOptions<T extends { id: number }> = {
  page?: number;
  limit?: number;
  search?: string;
  sortBy?: keyof T;
  sortOrder?: 1 | -1;
};

export const useEntity = <T extends { id: number }>(settings: EntitySettings) => {
  // use the useApi composable with the base URL of the Faker API
  const { get, post, put, del } = useApi('https://fakerapi.it/api/v2/');
};

Wie du siehst, haben wir einige Typen für die Entity-Einstellungen und die Fetch-Optionen definiert. Den Typ FetchListOptions solltest du je nach den Möglichkeiten der verwendeten API erweitern.

Einzelne Einträge abrufen

Als Nächstes ergänzen wir die Möglichkeit, einzelne Einträge per ID abzurufen.

composables/useEntity.ts
export const useEntity = <T extends { id: number }>(settings: EntitySettings) => {
  const { get, post, put, del } = useApi('https://fakerapi.it/api/v2/');

  // reactive variable to hold loading state
  const isLoadingItem = ref(false);
  // reactive variable to hold the fetched item
  const dataItem = ref<T | null>(null);

  // Fetch single item method
  const fetchItemById = async (id: number) => {
    isLoadingItem.value = true;

    const response = await get(`${settings.endpoint}/${id}`);
    dataItem.value = response || null;

    isLoadingItem.value = false;

    return dataItem.value;
  };
};

Eine Liste von Einträgen abrufen

Jetzt können wir die Möglichkeit ergänzen, eine Liste von Einträgen mit optionalen Parametern abzurufen.

composables/useEntity.ts
export const useEntity = <T extends { id: number }>(settings: EntitySettings) => {
  const { get, post, put, del } = useApi('https://fakerapi.it/api/v2/');

  // reactive variable to hold loading state
  const isLoadingItem = ref(false);
  // reactive variable to hold the fetched item
  const dataItem = ref<T | null>(null);

  // Fetch single item method
  const fetchItemById = async (id: number) => {
    isLoadingItem.value = true;

    const response = await get(`${settings.endpoint}/${id}`);
    dataItem.value = response || null;

    isLoadingItem.value = false;

    return dataItem.value;
  };

  // List configuration
  const isLoadingList = ref(false);
  const dataList = ref<T[]>([]);
  const total = ref(0);
  const page = ref(1);
  const limit = ref(10);

  // Filter configuration
  const searchValue = ref<string>('');
  const sortField = ref<keyof T>('id');
  const sortOrder = ref<1 | -1>(1);

  // Error configuration
  const errors = ref([]);

  // Helper function to create query params
  // This varies based on your API design
  const createQueryParams = (params: Partial<FetchListOptions<T>>): string => {
    const query = new URLSearchParams();

    if (params.page) {
      query.append('page', String(params.page));
    }
    if (params.limit) {
      query.append('limit', String(params.limit));
    }
    if (params.search && params.search.trim() !== '') {
      query.append('search', params.search);
    }
    if (params.sortBy) {
      query.append('sortBy', String(params.sortBy));
    }
    if (params.sortOrder) {
      query.append('sortOrder', params.sortOrder === -1 ? 'desc' : 'asc');
    }

    return query.toString();
  };

  // Fetch list methods
  const setFetchListOptions = (options: Partial<FetchListOptions<T>>) => {
    if (options.page) page.value = options.page;
    if (options.limit) limit.value = options.limit;
    if (options.search) searchValue.value = options.search;
    if (options.sortBy) sortField.value = options.sortBy;
    if (options.sortOrder) sortOrder.value = options.sortOrder;
  };

  const fetchList = async (fetchOptions?: Partial<FetchListOptions<T>>) => {
    if (fetchOptions) {
      setFetchListOptions(fetchOptions);
    }

    isLoadingList.value = true;

    const response = await get(
      `${settings.endpoint}?${createQueryParams({
        page: page.value,
        limit: limit.value,
        search: searchValue.value,
        sortBy: sortField.value,
        sortOrder: sortOrder.value,
      })}`
    );

    dataList.value = response[settings.listDataKey] || [];
    total.value = response.total || 0;

    isLoadingList.value = false;

    return dataList.value;
  };
};

Methoden zum Ändern

Nun können wir Methoden zum Erstellen, Aktualisieren und Löschen von Einträgen hinzufügen.

composables/useEntity.ts
export const useEntity = <T extends { id: number }>(settings: EntitySettings) => {
  const { get, post, put, del } = useApi('https://fakerapi.it/api/v2/');

  // reactive variable to hold loading state
  const isLoadingItem = ref(false);
  // reactive variable to hold the fetched item
  const dataItem = ref<T | null>(null);

  // Fetch single item method
  const fetchItemById = async (id: number) => {
    isLoadingItem.value = true;

    const response = await get(`${settings.endpoint}/${id}`);
    dataItem.value = response || null;

    isLoadingItem.value = false;

    return dataItem.value;
  };

  // List configuration
  const isLoadingList = ref(false);
  const dataList = ref<T[]>([]);
  const total = ref(0);
  const page = ref(1);
  const limit = ref(10);

  // Filter configuration
  const searchValue = ref<string>('');
  const sortField = ref<keyof T>('id');
  const sortOrder = ref<1 | -1>(1);

  // Error configuration
  const errors = ref([]);

  // Helper function to create query params
  // This varies based on your API design
  const createQueryParams = (params: Partial<FetchListOptions<T>>): string => {
    const query = new URLSearchParams();

    if (params.page) {
      query.append('page', String(params.page));
    }
    if (params.limit) {
      query.append('limit', String(params.limit));
    }
    if (params.search && params.search.trim() !== '') {
      query.append('search', params.search);
    }
    if (params.sortBy) {
      query.append('sortBy', String(params.sortBy));
    }
    if (params.sortOrder) {
      query.append('sortOrder', params.sortOrder === -1 ? 'desc' : 'asc');
    }

    return query.toString();
  };

  // Fetch list methods
  const setFetchListOptions = (options: Partial<FetchListOptions<T>>) => {
    if (options.page) page.value = options.page;
    if (options.limit) limit.value = options.limit;
    if (options.search) searchValue.value = options.search;
    if (options.sortBy) sortField.value = options.sortBy;
    if (options.sortOrder) sortOrder.value = options.sortOrder;
  };

  const fetchList = async (fetchOptions?: Partial<FetchListOptions<T>>) => {
    if (fetchOptions) {
      setFetchListOptions(fetchOptions);
    }

    isLoadingList.value = true;

    const response = await get(
      `${settings.endpoint}?${createQueryParams({
        page: page.value,
        limit: limit.value,
        search: searchValue.value,
        sortBy: sortField.value,
        sortOrder: sortOrder.value,
      })}`
    );

    dataList.value = response[settings.listDataKey] || [];
    total.value = response.total || 0;

    isLoadingList.value = false;

    return dataList.value;
  };

  // Mutation methods (create, update, delete)
  const createItem = async (itemData: Partial<T>) => {
    if (isCreatingItem.value) return;

    isCreatingItem.value = true;

    errors.value = [];

    try {
      dataItem.value = await post(settings.endpoint, itemData);
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while creating the item.'];
      throw error;
    } finally {
      isCreatingItem.value = false;
    }

    return dataItem.value;
  };

  const updateItem = async (id: number, itemData: Partial<T>) => {
    if (isUpdatingItem.value) return;

    isUpdatingItem.value = true;

    errors.value = [];

    try {
      dataItem.value = await put(`${settings.endpoint}/${id}`, itemData);
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while updating the item.'];
      throw error;
    } finally {
      isUpdatingItem.value = false;
    }

    return dataItem.value;
  };

  const deleteItem = async (id: number) => {
    if (isDeletingItem.value) return;

    isDeletingItem.value = true;

    errors.value = [];

    try {
      await del(`${settings.endpoint}/${id}`);
      dataItem.value = null;
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while deleting the item.'];
      throw error;
    } finally {
      isDeletingItem.value = false;
    }
  };
};

Filtern, Sortieren und Paginierung

Jetzt ergänzen wir einige Hilfsmethoden für das Filtern, Sortieren und die Paginierung.

composables/useEntity.ts
export const useEntity = <T extends { id: number }>(settings: EntitySettings) => {
  const { get, post, put, del } = useApi('https://fakerapi.it/api/v2/');

  // reactive variable to hold loading state
  const isLoadingItem = ref(false);
  // reactive variable to hold the fetched item
  const dataItem = ref<T | null>(null);

  // Fetch single item method
  const fetchItemById = async (id: number) => {
    isLoadingItem.value = true;

    const response = await get(`${settings.endpoint}/${id}`);
    dataItem.value = response || null;

    isLoadingItem.value = false;

    return dataItem.value;
  };

  // List configuration
  const isLoadingList = ref(false);
  const dataList = ref<T[]>([]);
  const total = ref(0);
  const page = ref(1);
  const limit = ref(10);

  // Filter configuration
  const searchValue = ref<string>('');
  const sortField = ref<keyof T>('id');
  const sortOrder = ref<1 | -1>(1);

  // Error configuration
  const errors = ref([]);

  // Helper function to create query params
  // This varies based on your API design
  const createQueryParams = (params: Partial<FetchListOptions<T>>): string => {
    const query = new URLSearchParams();

    if (params.page) {
      query.append('page', String(params.page));
    }
    if (params.limit) {
      query.append('limit', String(params.limit));
    }
    if (params.search && params.search.trim() !== '') {
      query.append('search', params.search);
    }
    if (params.sortBy) {
      query.append('sortBy', String(params.sortBy));
    }
    if (params.sortOrder) {
      query.append('sortOrder', params.sortOrder === -1 ? 'desc' : 'asc');
    }

    return query.toString();
  };

  // Fetch list methods
  const setFetchListOptions = (options: Partial<FetchListOptions<T>>) => {
    if (options.page) page.value = options.page;
    if (options.limit) limit.value = options.limit;
    if (options.search) searchValue.value = options.search;
    if (options.sortBy) sortField.value = options.sortBy;
    if (options.sortOrder) sortOrder.value = options.sortOrder;
  };

  const fetchList = async (fetchOptions?: Partial<FetchListOptions<T>>) => {
    if (fetchOptions) {
      setFetchListOptions(fetchOptions);
    }

    isLoadingList.value = true;

    const response = await get(
      `${settings.endpoint}?${createQueryParams({
        page: page.value,
        limit: limit.value,
        search: searchValue.value,
        sortBy: sortField.value,
        sortOrder: sortOrder.value,
      })}`
    );

    dataList.value = response[settings.listDataKey] || [];
    total.value = response.total || 0;

    isLoadingList.value = false;

    return dataList.value;
  };

  // Mutation methods (create, update, delete)
  const createItem = async (itemData: Partial<T>) => {
    if (isCreatingItem.value) return;

    isCreatingItem.value = true;

    errors.value = [];

    try {
      dataItem.value = await post(settings.endpoint, itemData);
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while creating the item.'];
      throw error;
    } finally {
      isCreatingItem.value = false;
    }

    return dataItem.value;
  };

  const updateItem = async (id: number, itemData: Partial<T>) => {
    if (isUpdatingItem.value) return;

    isUpdatingItem.value = true;

    errors.value = [];

    try {
      dataItem.value = await put(`${settings.endpoint}/${id}`, itemData);
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while updating the item.'];
      throw error;
    } finally {
      isUpdatingItem.value = false;
    }

    return dataItem.value;
  };

  const deleteItem = async (id: number) => {
    if (isDeletingItem.value) return;

    isDeletingItem.value = true;

    errors.value = [];

    try {
      await del(`${settings.endpoint}/${id}`);
      dataItem.value = null;
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while deleting the item.'];
      throw error;
    } finally {
      isDeletingItem.value = false;
    }
  };

  // Expose methods and state
  const setPage = (newPage: number) => {
    page.value = newPage;
  };

  const setLimit = (newLimit: number) => {
    limit.value = newLimit;
  };

  const setSearchValue = (newSearch: string) => {
    searchValue.value = newSearch;
  };

  const setSortField = (newSortField: keyof T) => {
    sortField.value = newSortField;
  };

  const setSortOrder = (newSortOrder: 1 | -1) => {
    sortOrder.value = newSortOrder;
  };

  const setSort = (newSortField: keyof T, newSortOrder: 1 | -1) => {
    setSortField(newSortField);
    setSortOrder(newSortOrder);
  };
};

Alle Methoden und den Zustand zurückgeben

Zum Schluss geben wir alle Methoden und den Zustand zurück, die wir im Composable erstellt haben.

composables/useEntity.ts
export const useEntity = <T extends { id: number }>(settings: EntitySettings) => {
  const { get, post, put, del } = useApi('https://fakerapi.it/api/v2/');

  // reactive variable to hold loading state
  const isLoadingItem = ref(false);
  // reactive variable to hold the fetched item
  const dataItem = ref<T | null>(null);

  // Fetch single item method
  const fetchItemById = async (id: number) => {
    isLoadingItem.value = true;

    const response = await get(`${settings.endpoint}/${id}`);
    dataItem.value = response || null;

    isLoadingItem.value = false;

    return dataItem.value;
  };

  // List configuration
  const isLoadingList = ref(false);
  const dataList = ref<T[]>([]);
  const total = ref(0);
  const page = ref(1);
  const limit = ref(10);

  // Filter configuration
  const searchValue = ref<string>('');
  const sortField = ref<keyof T>('id');
  const sortOrder = ref<1 | -1>(1);

  // Error configuration
  const errors = ref([]);

  // Helper function to create query params
  // This varies based on your API design
  const createQueryParams = (params: Partial<FetchListOptions<T>>): string => {
    const query = new URLSearchParams();

    if (params.page) {
      query.append('page', String(params.page));
    }
    if (params.limit) {
      query.append('limit', String(params.limit));
    }
    if (params.search && params.search.trim() !== '') {
      query.append('search', params.search);
    }
    if (params.sortBy) {
      query.append('sortBy', String(params.sortBy));
    }
    if (params.sortOrder) {
      query.append('sortOrder', params.sortOrder === -1 ? 'desc' : 'asc');
    }

    return query.toString();
  };

  // Fetch list methods
  const setFetchListOptions = (options: Partial<FetchListOptions<T>>) => {
    if (options.page) page.value = options.page;
    if (options.limit) limit.value = options.limit;
    if (options.search) searchValue.value = options.search;
    if (options.sortBy) sortField.value = options.sortBy;
    if (options.sortOrder) sortOrder.value = options.sortOrder;
  };

  const fetchList = async (fetchOptions?: Partial<FetchListOptions<T>>) => {
    if (fetchOptions) {
      setFetchListOptions(fetchOptions);
    }

    isLoadingList.value = true;

    const response = await get(
      `${settings.endpoint}?${createQueryParams({
        page: page.value,
        limit: limit.value,
        search: searchValue.value,
        sortBy: sortField.value,
        sortOrder: sortOrder.value,
      })}`
    );

    dataList.value = response[settings.listDataKey] || [];
    total.value = response.total || 0;

    isLoadingList.value = false;

    return dataList.value;
  };

  // Mutation methods (create, update, delete)
  const createItem = async (itemData: Partial<T>) => {
    if (isCreatingItem.value) return;

    isCreatingItem.value = true;

    errors.value = [];

    try {
      dataItem.value = await post(settings.endpoint, itemData);
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while creating the item.'];
      throw error;
    } finally {
      isCreatingItem.value = false;
    }

    return dataItem.value;
  };

  const updateItem = async (id: number, itemData: Partial<T>) => {
    if (isUpdatingItem.value) return;

    isUpdatingItem.value = true;

    errors.value = [];

    try {
      dataItem.value = await put(`${settings.endpoint}/${id}`, itemData);
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while updating the item.'];
      throw error;
    } finally {
      isUpdatingItem.value = false;
    }

    return dataItem.value;
  };

  const deleteItem = async (id: number) => {
    if (isDeletingItem.value) return;

    isDeletingItem.value = true;

    errors.value = [];

    try {
      await del(`${settings.endpoint}/${id}`);
      dataItem.value = null;
    } catch (error: any) {
      errors.value = error.message || ['An error occurred while deleting the item.'];
      throw error;
    } finally {
      isDeletingItem.value = false;
    }
  };

  // Expose methods and state
  const setPage = (newPage: number) => {
    page.value = newPage;
  };

  const setLimit = (newLimit: number) => {
    limit.value = newLimit;
  };

  const setSearchValue = (newSearch: string) => {
    searchValue.value = newSearch;
  };

  const setSortField = (newSortField: keyof T) => {
    sortField.value = newSortField;
  };

  const setSortOrder = (newSortOrder: 1 | -1) => {
    sortOrder.value = newSortOrder;
  };

  const setSort = (newSortField: keyof T, newSortOrder: 1 | -1) => {
    setSortField(newSortField);
    setSortOrder(newSortOrder);
  };

  return {
    // list
    setFetchListOptions,
    fetchList,
    isLoadingList,
    dataList,

    // single item
    fetchItemById,
    isLoadingItem,
    dataItem,

    // mutations
    createItem,
    isCreatingItem,
    updateItem,
    isUpdatingItem,
    deleteItem,
    isDeletingItem,

    // pagination and filters
    total,
    page,
    limit,
    searchValue,
    sortField,
    sortOrder,
    setPage,
    setLimit,
    setSearchValue,
    setSortField,
    setSortOrder,
    setSort,

    // errors
    errors,
  };
};

Spezifisches Entity-Composable für die Entität User

Da wir jetzt ein generisches Entity-Composable haben, können wir spezifische Composables für verschiedene Entitäten erstellen.

Beginnen wir mit einem Composable für die Entität User:

composables/entities/useUsers.ts
import { useEntity } from '../useEntity';

// Define the User type
export type User = {
  id: number;
  firstname: string;
  lastname: string;
  email: string;
};

export const useUsers = () => {
  // utilize the generic useEntity composable for the User entity
  const userEntity = useEntity<User>({
    endpoint: 'users',
    listDataKey: 'data',
  });

  return {
    // return all methods and state from the user entity composable
    ...userEntity,
  };
};

Das war's! Wir haben jetzt ein einfaches, wiederverwendbares API-Fetch-Composable in Vue.js, das sich leicht für verschiedene Entitäten erweitern lässt.

Aliase für Methoden und Eigenschaften

Um das spezifische Entity-Composable einfacher nutzbar zu machen, können wir einige Aliase für Methoden und Eigenschaften anlegen.

composables/entities/useUsers.ts
import { useEntity } from '../useEntity';

// Define the User type
export type User = {
  id: number;
  firstname: string;
  lastname: string;
  email: string;
};

export const useUsers = () => {
  // utilize the generic useEntity composable for the User entity
  const userEntity = useEntity<User>({
    endpoint: 'users',
    listDataKey: 'data',
  });

  return {
    // return all methods and state from the user entity composable
    ...userEntity,

    // aliases for user specific methods and properties
    users: userEntity.dataList,
    user: userEntity.dataItem,

    totalUsers: userEntity.total,

    isLoadingUsers: userEntity.isLoadingList,
    isLoadingUser: userEntity.isLoadingItem,
    isCreatingUser: userEntity.isCreatingItem,
    isUpdatingUser: userEntity.isUpdatingItem,
    isDeletingUser: userEntity.isDeletingItem,

    fetchUsers: userEntity.fetchList,
    fetchUserById: userEntity.fetchItemById,

    createUser: userEntity.createItem,
    updateUser: userEntity.updateItem,
    deleteUser: userEntity.deleteItem,
  };
};

Verwendung

Nun können wir das spezifische Entity-Composable in unseren Vue-Komponenten verwenden.

src/components/UserList.vue
<template>
  <div>
    User List Component

    <div v-if="isLoadingUsers">Loading users...</div>

    <ul v-if="!isLoadingUsers">
      <li
        v-for="user in users"
        :key="user.id"
      >
        {{ user.firstname }} {{ user.lastname }} - {{ user.email }}
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue';
import { useUsers } from '../composables/entities/useUsers';

const { isLoadingUsers, users, fetchUsers } = useUsers();

onMounted(async () => {
  await fetchUsers();
});
</script>

Wie du siehst, haben wir unsere Komponente UserList so angepasst, dass sie das Composable useUsers verwendet. Sie ist jetzt viel aufgeräumter und hat auch einen Ladezustand.

Abschließende Hinweise

Wie dir vielleicht aufgefallen ist, unterstützt die Faker API keine REST-Operationen wie Erstellen, Aktualisieren oder Löschen und auch keine Einzelansichten. Für eine voll funktionsfähige REST-API kannst du hier eine Mock-API erstellen: (https://jsoning.com/api/).

Vergiss nicht, die apiUrl im Composable useEntity auf deine Mock-API zu ändern.

Kommentare

Die Kommentare sind GitHub Discussions: Zum Schreiben mit GitHub anmelden.

Die Kommentare kommen über giscus von GitHub. Beim Laden gehen deine IP-Adresse an giscus.app und GitHub.