Нескінченна стрічка

Стрічка, що ніколи не закінчується, мусить завантажувати дані, поки глядач дивиться: досить рано, щоб наступний допис уже був на місці, з чимось на екрані, поки сторінка в дорозі чи не завантажилася, і без запиту, що лишився працювати після того, як стрічки вже немає.

Спробуйте

Кожна версія імпортує ./api, що заміняє ваш бекенд: fetchPosts(page, signal) повертає одну сторінку дописів і зупиняється, коли сигнал скасовано. Замініть його викликом вашого API й приберіть failNextRequest, який лише керує перемикачем збою в демо.

typescript
import { createSignal } from '@reelkit/core';
import { cdnUrl } from '@reelkit/example-data';

// Stands in for your backend. The recipe only relies on the signature of
// `fetchPosts`: a page number in, a page of posts out, cancellable through an
// AbortSignal.

export interface Post {
  id: string;
  title: string;
  image: string;
}

const kPageSize = 6;
const kDelayMs = 800;
const kImages = Array.from(
  { length: 9 },
  (_, i) => `samples/images/image-${String(i + 1).padStart(2, '0')}.jpg`,
);

/**
 * Demo switch: while on, the next request fails, so the error slide can be
 * seen. A signal, so the switch shows when a request has used it up. Only the
 * demo on this page uses it: delete it, and the `@reelkit/core` import with
 * it, when you replace `fetchPosts` with your API.
 */
export const failNextRequest = createSignal(false);

export function fetchPosts(page: number, signal: AbortSignal): Promise<Post[]> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      if (failNextRequest.value) {
        failNextRequest.value = false;
        reject(new Error('The feed could not be loaded.'));
        return;
      }
      resolve(
        Array.from({ length: kPageSize }, (_, i) => {
          const n = page * kPageSize + i;
          return {
            id: `post-${n}`,
            title: `Post ${n + 1}`,
            image: cdnUrl(kImages[n % kImages.length]),
          };
        }),
      );
    }, kDelayMs);

    signal.addEventListener(
      'abort',
      () => {
        clearTimeout(timer);
        reject(signal.reason);
      },
      { once: true },
    );
  });
}
Зразкові медіа працюють лише на reelkit.dev

cdnUrl будує посилання на cdn.reelkit.dev, який віддає файли лише сайтам ReelKit. У вашому застосунку ці зображення не завантажаться, тож перш ніж копіювати цей файл, спрямуйте image на власні медіа.

InfiniteFeed.tsx
InfiniteFeed.vue
infinite-feed.component.ts

Гортайте вгору або натисніть ↓: нова сторінка надходить за три дописи до кінця. Натисніть «Fail next request», щоб побачити слайд помилки та його кнопку Retry.

Чому це працює

  • Кількість росте, слайдів не більшає. count — це кількість дописів плюс один, а в DOM завжди лише три слайди, тож стрічка з тисяч дописів коштує стільки ж, скільки з десяти. Див. віртуалізацію.
  • Останній слайд показує стан. Додатковий слайд після дописів показує «Loading…», поки сторінка в дорозі, і помилку з Retry, коли вона не завантажилася, тож глядач ніколи не гортає в порожнечу. Це ще один випадок у itemBuilder, який рендерить усе, що стоїть за індексом.
  • Завантаження наперед, від зміни слайда. afterChange просить наступну сторінку, щойно глядач опиняється за три дописи до кінця. Сторінка, що не завантажилася, чекає на Retry, тож гортання туди й назад не повторює запит, що падає. Цей колбек описано разом з іншими подіями для React, Vue та Angular.
  • Ключі йдуть за дописами. keyExtractor повертає id кожного допису, тож слайд зберігає свій елемент, коли позаду нього зʼявляється нова сторінка, а не перебудовується за індексом. Це пропс у React, Vue та Angular однаково.
  • Зображення вантажаться раніше за свій слайд. Слайд нижче ще не змонтовано, бо змонтовано лише видимий діапазон, як пояснює віртуалізація. Тож зображення кожної нової сторінки запитуються щойно вона надходить і беруться з кешу браузера, коли їхній слайд монтується.
  • Клавіші й колесо вимкнено навмисно. enableNavKeys слухає все вікно, а enableWheel перехоплює прокручування сторінки, поки вказівник над стрічкою. Обидва пасують стрічці на весь екран і заважають сторінці, у яку стрічку вбудовано, як тут, тож вони вимкнені, а дві кнопки натомість викликають prev() і next() з Reel API. Свайп працює в будь-якому разі.
  • Ніщо не переживає стрічку. Кожен запит має AbortController, який скасовується, коли стрічка демонтується, тож пізня відповідь ніколи не пише в стрічку, якої вже немає.

Наступні кроки