[譯] 使用響應式程式設計來實現簡易版的無限滾動載入

SangKa發表於2018-03-07

原文連結: hackernoon.com/naive-infin…

本文為 RxJS 中文社群 翻譯文章,如需轉載,請註明出處,謝謝合作!

如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點選【這裡】

這是一次嘗試,使用 RxJS 來實現簡單的無限滾動載入。

Angular 版本實現的文章: 使用 RxJS Observables 來實現簡易版的無限滾動載入指令

什麼是響應式程式設計?

簡單來說,它使用非同步資料流進行程式設計。這有一篇 Andre Staltz 所寫的超棒文章及 egghead 提供的配套視訊:

文章: 不容錯過的響應式程式設計介紹

視訊: egghead.io/courses/int…

什麼是 RxJS?

RxJS 或 Reactive Extensions 是最先由 Microsoft Open Technologies 開發的用於轉換、組合和查詢資料流的庫。github.com/Reactive-Ex… (譯者注: 這是 V4 版本的 RxJS)

這是 Ben Lesh 的演講 用響應式的思維來使用 RxJS 5,非常棒。

下面是 Netanel Basal 對 Observables 和少數操作符的一些精彩介紹:

  1. Observables 揭祕
  2. RxJS: 6個你必須知道的操作符 (簡體中文)

我們要做什麼?

我們將要使用 observables 來開發一個簡單的無限滾動載入。當使用者滾動到指定容器高度的70%,我們就呼叫 API 從伺服器獲取更多的資料。我們會使用 HackerNews 的非官方 API 來獲取最新的新聞。

下面是我們將使用到的 RxJS 操作符:

  1. map : 與陣列的 map 類似,對映傳入的資料流。
  2. filter : 與陣列的 filter 類似,過濾傳入的資料流。
  3. pairwise : 返回由當前發出值和前一個發出值組成的陣列。
  4. startWith : 返回的 observable 會在發出源 observable 的值之前先發出提供的值。
  5. exhaustMap : 只有當內部 observable 完成後,才會發出新的值。

jsbin.com 上的完整示例: output.jsbin.com/punibux

階段一: 設定基礎 html 和 css

匯入 RxJS 庫並使用 infinite-scroller 作為滾動容器的 id,獲取的所有新聞都將追加到此容器中。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Naive Infinite Scroller - RxJS</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.min.js"></script>
</head>
<body>
  <ul id="infinite-scroller">
  </ul>
</body>
</html>
複製程式碼
#infinite-scroller {
  height: 500px;
  width: 700px;
  border: 1px solid #f5ad7c;
  overflow: scroll;
  padding: 0;
  
  li {
    padding : 10px 5px;
    line-height: 1.5;
    &:nth-child(odd) {
      background : #ffe8d8;
    }
    &:nth-child(even) {
      background : #f5ad7c;
    }
  }
}
複製程式碼

階段二: 設定輔助函式,用來處理資料、渲染和計算

let currentPage = 1;

const getQuotesAPI = () => {
 return 'https://node-hnapi.herokuapp.com/news?page=' + currentPage; 
};

/** 
  處理 API 返回的資料
**/
const processData = res => {
  res.json()
     .then(news => {
       currentPage++;
       news.forEach(renderNews);
     });
};

/** 
  
  渲染每條資訊
**/
const renderNews = (news) => {
   const li = document.createElement('li');
   li.innerHTML = `${news.id} - ${news.title}`;
   scrollElem.appendChild(li);
};

/**
  檢查使用者是否向下滾動,通過前一個滾動位置
  和當前滾動位置進行判斷
**/
const isUserScrollingDown = (positions) => {
  return positions[0].sT < positions[1].sT;
};

/** 
  檢查滾動位置是否達到了要求的容器百分比高度
**/
const isScrollExpectedPercent = (position, percent) => {
  return ((position.sT + position.cH) / position.sH) > (percent/100);
};
複製程式碼

前面三個函式都很簡單:

  1. getQuotesAPI — 返回 API 的 url,此 url 使用當前頁碼作為查詢引數。
  2. processData — 處理 fetch API 返回的資料並增加當前頁碼。
  3. renderNews — 接收每條新聞資料並將其渲染到頁面中。

後面兩個函式用來進行滾動計算:

  1. isUserScrollingDown — 檢測使用者是否向下滾動。
  2. isScrollExpectedPercent — 檢測使用者是否已經滾動指定的百分比,從而載入更多資料。

階段三: 設定 observable 流

/**
  設定流
**/
const scrollElem = document.getElementById('infinite-scroller');
const scrollEvent$ = Rx.Observable.fromEvent(scrollElem, 'scroll');
複製程式碼

要捕獲容器的滾動事件,我們需要建立滾動事件的 observable 。使用 Rx.Observable.fromEvent 就可以完成。在變數結尾處加 $ 是一種慣例,以表示引用的是 observable 流。

階段四: 編寫流的邏輯,負責處理滾動事件和呼叫 API

/**
  流的邏輯
**/
const userScrolledDown$ = scrollEvent$
  .map(e => ({ 
    sH: e.target.scrollHeight,
    sT: e.target.scrollTop,
    cH: e.target.clientHeight
  }))
  .pairwise()
  .filter(positions =>  {
     return isUserScrollingDown(positions) && isScrollExpectedPercent(positions[1], 70))
  });

const requestOnScroll$ = userScrolledDown$
  .startWith([])
  .exhaustMap(() => Rx.Observable.fromPromise(fetch(getQuotesAPI())))

/**
  訂閱以產生效果
**/
requestOnScroll$.subscribe(processData);
複製程式碼

我們將接收由 scrollEvent$ 發出的滾動事件並將其對映成無限滾動載入邏輯所需要的值。我們只取滾動元素的三個屬性: scrollHeightscrollTopclientHeight.

將對映過的資料傳給 pairwise 操作符,它會發出由當前值和前一個值組成的陣列,如下圖所示。

[譯] 使用響應式程式設計來實現簡易版的無限滾動載入

現在我們將這組位置資料傳給 filter 操作符來根據條件進行過濾:

  1. 使用者是否向下滾動
  2. 使用者是否已經滾動到容器的70%高度

userScrollDown$ 產生符合過濾條件的值後會呼叫 requestOnScroll$ 。我們給了 requestOnScroll$ 一個空陣列作為初始值。

[譯] 使用響應式程式設計來實現簡易版的無限滾動載入

我們使用 Rx.Observable.fromPromise 來將 promise 轉化成 observable 。fetch 發起 http 請求並返回 promise 。exhaustMap 會進行等待,直到 fetch 完成並且內部 observable 發出 API 返回的資料。

Observables 是懶載入的。這意味著除非訂閱了它們,它們才會執行。我們訂閱 requestOnScroll$ 並傳入 processData 作為訂閱方法。當 exhaustMap 發出 API 的返回資料後,資料會傳給 processData,然後執行 renderNews 將資料渲染到頁面中。

下面的 gif 圖片實際演示了無限滾動載入的效果,注意觀察右邊的滾動條。

result

在我的下篇文章中,我將會嘗試在 Angular 中建立一個無限滾動載入指令來實現它。

更新: 這是我下篇文章的連結 使用 RxJS Observables 來實現簡易版的無限滾動載入指令

相關文章