Skip to content

eldenhard/suggest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

26 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Vue Suggestions Component with Debounce and API Integration

Π­Ρ‚ΠΎΡ‚ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΠ΅Ρ‚ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ Π½Π° Vue 3 для отобраТСния ΠΏΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠΉ ΠΈΠ· API, Π²ΠΊΠ»ΡŽΡ‡Π°Ρ Π·Π°Π΄Π΅Ρ€ΠΆΠΊΡƒ запросов (debounce), Π°Π΄Π°ΠΏΡ‚ΠΈΠ²Π½Ρ‹ΠΉ интСрфСйс, ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ ошибок ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ. ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ прСдоставляСт ΠΏΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡƒΡŽ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρƒ для API-взаимодСйствия ΠΈ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ Π΄ΠΈΠ½Π°ΠΌΠΈΡ‡Π΅ΡΠΊΡƒΡŽ настройку для Ρ€Π°Π·Π½Ρ‹Ρ… источников Π΄Π°Π½Π½Ρ‹Ρ….


πŸ“‹ ΠžΡΠΎΠ±Π΅Π½Π½ΠΎΡΡ‚ΠΈ

  • Π—Π°Π΄Π΅Ρ€ΠΆΠΊΠ° запросов ΠΊ API: Π£ΠΌΠ΅Π½ΡŒΡˆΠ°Π΅Ρ‚ количСство запросов, оТидая Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΡ Π²Π²ΠΎΠ΄Π°.
  • ИспользованиС composable: Π›Π΅Π³ΠΊΠΎ интСгрируСтся Π² Π΄Ρ€ΡƒΠ³ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с API.
  • ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° Ρ€Π°Π·Π½Ρ‹Ρ… API: ΠŸΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ‚ Π½Π°ΡΡ‚Ρ€Π°ΠΈΠ²Π°Ρ‚ΡŒ Ρ‚Ρ€Π°Π½ΡΡ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² запроса.
  • Адаптивный интСрфСйс: UI ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎ отобраТаСтся Π½Π° всСх устройствах.
  • ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок: Π£Π΄ΠΎΠ±Π½Ρ‹Π΅ сообщСния для ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ.
  • БостояниС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ: ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΈΠ½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ.
  • ΠŸΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΠΎΡΡ‚ΡŒ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°: Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΏΠΎΠ΄ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ садТСст ΠΏΠΎΠ΄ свои Π½ΡƒΠΆΠ΄Ρ‹
  • РСализация Π±Π΅Π· 3rd-party: РСализация Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π½Π° Vue+TS

πŸ“ Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°

project
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ components
β”‚   β”‚   β”œβ”€β”€ VSuggest.vue
β”‚   β”‚   β”œβ”€β”€ VSuggestItem.vue
β”‚   β”‚   β”œβ”€β”€ VTag.vue
β”‚   β”‚   β”œβ”€β”€ VLoader.vue
β”‚   β”‚   β”œβ”€β”€ VCompanyEntity.vue
β”‚   β”‚   β”œβ”€β”€ VUserEntity.vue
β”‚   β”œβ”€β”€ composables
β”‚   β”‚   └── useGetFetchSuggestions.ts
β”‚   β”œβ”€β”€ utils
β”‚   β”‚   └── debounce.ts
β”‚   β”‚   └── clickOutside.ts
β”‚   β”œβ”€β”€ types
β”‚   β”‚   └── index.ts
β”‚   β”œβ”€β”€ App.vue
β”‚   └── main.ts
β”œβ”€β”€ public
β”‚   └── assets
β”‚       └── images
β”‚            └── noPhoto.png
β”‚       └── styles
β”‚            └── style.css
β”‚            └── suggestItemStyle.css
β”œβ”€β”€ package.json
└── README.md

πŸ”§ ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹

0. App.vue

Π‘Ρ‚Π°Ρ€Ρ‚ΠΎΠ²Ρ‹ΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚

<template>
    <VSuggest :inputLabel="'ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ ΠΈΠ»ΠΈ компания'" 
      :tagAmount="1" 
      :placeholderSuggest="'Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π»ΠΎΠ³ΠΈΠ½'" 
      :apiUrl="'https://habr.com/kek/v2/publication/suggest-mention'"   
      />
</template>

πŸ”‘ ΠšΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ возмоТности:

  • Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ настройки количСства сущностСй допустимых для Π²Ρ‹Π±ΠΎΡ€Π°
  • Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ настройки placeholder ΠΈ label
  • Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… URl, Π½ΠΎ с ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ‡Π½ΠΎΠΉ структурой

1. VSuggest.vue

Основной ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚, ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°ΡŽΡ‰ΠΈΠΉ Π²Π²ΠΎΠ΄ ΠΈ ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°ΡŽΡ‰ΠΈΠΉ список ΠΏΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠΉ.

πŸ”‘ ΠšΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ возмоТности:

  • ПолС Π²Π²ΠΎΠ΄Π° с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ запросов (debounced fetchSuggestions).
  • Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΡ с ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°ΠΌΠΈ VLoader ΠΈ VSuggestItem.
  • ДинамичСская ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок ΠΈ пустых состояний.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΊΠΎΠ΄Π°:

<template>
  <main>
    <section>
      <div>
        <label for="suggest" class="label_description">
          <span>*</span>
          ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ ΠΈΠ»ΠΈ компания
        </label>
        <br />
        <div class="input_block">
          <VTag @removeItem="removeItem" :listItem="listItem" />

          <input
            id="suggest"
            type="text"
            ref="inputUserValue"
            v-model="query"
            @input="debouncedFetchSuggestions"
            :disabled="listItem.length >= 1"
            :placeholder="listItem.length >= 1 ? '' : 'Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π»ΠΎΠ³ΠΈΠ½'"
            @keydown.enter.prevent="addItemToList(query)"
            aria-autocomplete="list"
            aria-expanded="true"
          />
          <VLoader v-if="isLoading" />
        </div>
        <p v-if="error" class="error-message">{{ error }}</p>
      </div>
      <VSuggestItem :responseData="responseData" @selectedItem="addItemToList" v-if="flagActiveList" />
    </section>
  </main>
</template>

2. VSuggestItem.vue

ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ для отобраТСния списка ΠΏΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠΉ.

πŸ› οΈ Бвойства:

responseData: массив ΠΏΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠΉ ΠΈΠ· API.

🎯 Бобытия:

@selectedItem: отправляСт Π²Ρ‹Π±Ρ€Π°Π½Π½ΠΎΠ΅ ΠΏΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎ Π² Ρ€ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΡΠΊΠΈΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚.

3. VLoader.vue

ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚, ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°ΡŽΡ‰ΠΈΠΉ ΠΈΠ½Π΄ΠΈΠΊΠ°Ρ‚ΠΎΡ€ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ.

4. VTag.vue

ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ для отобраТСния Π²Ρ‹Π±Ρ€Π°Π½Π½Ρ‹Ρ… Ρ‚Π΅Π³ΠΎΠ² с Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒΡŽ ΠΈΡ… удалСния.

4. VCompanyEntity.vue

ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ для отобраТСния Π΄Π°Π½Π½Ρ‹Ρ… ΠΊΠΎΠ³Π΄Π° Ρ‚ΠΈΠΏ = компания

4. VUserEntity.vue

ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ для отобраТСния Π΄Π°Π½Π½Ρ‹Ρ… ΠΊΠΎΠ³Π΄Π° Ρ‚ΠΈΠΏ != компания

πŸ“œ Composables

useGetFetchSuggestions.ts

Composable для взаимодСйствия с API.

πŸ”§ ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹:

interface FetchSuggestionsOptions<T> {
  apiUrl: string; // URL API
  transformResponse?: (data: any) => T[]; // Π›ΠΎΠ³ΠΈΠΊΠ° трансформации Π΄Π°Π½Π½Ρ‹Ρ…
  validateQueryParams?: (query: string) => boolean; // Π›ΠΎΠ³ΠΈΠΊΠ° ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ запроса
}

πŸ› οΈ Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅:

{
  isLoading: Ref<boolean>,
  error: Ref<string | null>,
  responseData: Ref<T[]>,
  fetchSuggestions: (query: string) => void
}

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ использования:

const { isLoading, error, responseData, fetchSuggestions } = useGetFetchSuggestions({
  apiUrl: "https://api.example.com/suggestions",
  transformResponse: (data) => data.items,
  validateQueryParams: (query) => query.length >= 3,
});

βš™οΈ Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹

debounce.ts

Ѐункция для Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ выполнСния запросов.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΊΠΎΠ΄Π°:

export function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
  let timeout: ReturnType<typeof setTimeout>;

  return ((...args: Parameters<T>) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn(...args), delay);
  }) as T;
}

🎨 Π‘Ρ‚ΠΈΠ»ΠΈ

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Π°Π΄Π°ΠΏΡ‚ΠΈΠ²Π½ΠΎΠ³ΠΎ Π΄ΠΈΠ·Π°ΠΉΠ½Π°:

.suggest-container {
  width: clamp(20%, 35%, 80%);
  border-radius: 4px;
  box-shadow: rgba(0, 0, 0, 0.25) 0px 0.0625em 0.0625em,
              rgba(0, 0, 0, 0.25) 0px 0.125em 0.5em,
              rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset;
  overflow-y: auto;
}

.avatar {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 8px;
}

@media (max-width: 768px) {
  .suggest-container {
    width: 80%;
  }
}

πŸ“‘ Π Π°Π±ΠΎΡ‚Π° с API

ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ API:

const { isLoading, error, responseData, fetchSuggestions } = useGetFetchSuggestions({
  apiUrl: "https://habr.com/kek/v2/publication/suggest-mention",
  transformResponse: (data) => data.data,
  validateQueryParams: (query) => query.trim().length >= 3,
});

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок:

  • 400 Error: ΠžΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°Π΅Ρ‚ сообщСниС "НСкоррСктный запрос. ΠŸΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΡƒ."
  • 500 Error: ΠžΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°Π΅Ρ‚ сообщСниС "Ошибка сСрвСра. ΠŸΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚Π΅ ΠΏΠΎΠ·ΠΆΠ΅."

πŸ›  Установка ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°

ΠšΠ»ΠΎΠ½ΠΈΡ€ΡƒΠΉΡ‚Π΅ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ:

git clone https://github.com/eldenhard/suggest.git

УстановитС зависимости:

npm install

ЗапуститС сСрвСр Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ:

npm run dev

About

No description or website provided.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published