原文連結: hackernoon.com/naive-infin…
本文為 RxJS 中文社群 翻譯文章,如需轉載,請註明出處,謝謝合作!
如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點選【這裡】
這是一次嘗試,使用 RxJS 來實現簡單的無限滾動載入。
Angular 版本實現的文章: 使用 RxJS Observables 來實現簡易版的無限滾動載入指令
什麼是響應式程式設計?
簡單來說,它使用非同步資料流進行程式設計。這有一篇 Andre Staltz 所寫的超棒文章及 egghead 提供的配套視訊:
文章: 不容錯過的響應式程式設計介紹
什麼是 RxJS?
RxJS 或 Reactive Extensions 是最先由 Microsoft Open Technologies 開發的用於轉換、組合和查詢資料流的庫。github.com/Reactive-Ex… (譯者注: 這是 V4 版本的 RxJS)
這是 Ben Lesh 的演講 用響應式的思維來使用 RxJS 5,非常棒。
下面是 Netanel Basal 對 Observables 和少數操作符的一些精彩介紹:
我們要做什麼?
我們將要使用 observables 來開發一個簡單的無限滾動載入。當使用者滾動到指定容器高度的70%,我們就呼叫 API 從伺服器獲取更多的資料。我們會使用 HackerNews 的非官方 API 來獲取最新的新聞。
下面是我們將使用到的 RxJS 操作符:
- map : 與陣列的 map 類似,對映傳入的資料流。
- filter : 與陣列的 filter 類似,過濾傳入的資料流。
- pairwise : 返回由當前發出值和前一個發出值組成的陣列。
- startWith : 返回的 observable 會在發出源 observable 的值之前先發出提供的值。
- 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);
};
複製程式碼
前面三個函式都很簡單:
getQuotesAPI
— 返回 API 的 url,此 url 使用當前頁碼作為查詢引數。processData
— 處理 fetch API 返回的資料並增加當前頁碼。renderNews
— 接收每條新聞資料並將其渲染到頁面中。
後面兩個函式用來進行滾動計算:
isUserScrollingDown
— 檢測使用者是否向下滾動。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
$ 發出的滾動事件並將其對映成無限滾動載入邏輯所需要的值。我們只取滾動元素的三個屬性: scrollHeight
、 scrollTop
和 clientHeight
.
將對映過的資料傳給 pairwise
操作符,它會發出由當前值和前一個值組成的陣列,如下圖所示。
現在我們將這組位置資料傳給 filter
操作符來根據條件進行過濾:
- 使用者是否向下滾動
- 使用者是否已經滾動到容器的70%高度
當 userScrollDown$
產生符合過濾條件的值後會呼叫 requestOnScroll$
。我們給了 requestOnScroll$
一個空陣列作為初始值。
我們使用 Rx.Observable.fromPromise
來將 promise 轉化成 observable 。fetch
發起 http 請求並返回 promise 。exhaustMap
會進行等待,直到 fetch 完成並且內部 observable 發出 API 返回的資料。
Observables 是懶載入的。這意味著除非訂閱了它們,它們才會執行。我們訂閱 requestOnScroll$
並傳入 processData
作為訂閱方法。當 exhaustMap
發出 API 的返回資料後,資料會傳給 processData
,然後執行 renderNews
將資料渲染到頁面中。
下面的 gif 圖片實際演示了無限滾動載入的效果,注意觀察右邊的滾動條。
在我的下篇文章中,我將會嘗試在 Angular 中建立一個無限滾動載入指令來實現它。
更新: 這是我下篇文章的連結 使用 RxJS Observables 來實現簡易版的無限滾動載入指令