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

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

本文爲 RxJS 中文社區 翻譯文章,如需轉載,請註明出處,謝謝合做!css

若是你也想和咱們一塊兒,翻譯更多優質的 RxJS 文章以奉獻給你們,請點擊【這裏】html

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

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

什麼是響應式編程?

簡單來講,它使用異步數據流進行編程。這有一篇 Andre Staltz 所寫的超棒文章及 egghead 提供的配套視頻:react

文章: 不容錯過的響應式編程介紹git

視頻: egghead.io/courses/int…es6

什麼是 RxJS?

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

這是 Ben Lesh 的演講 用響應式的思惟來使用 RxJS 5,很是棒。ajax

下面是 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 來實現簡易版的無限滾動加載指令

相關文章
相關標籤/搜索