原文链接:
本文为 翻译文章,如需转载,请注明出处,谢谢合作!
如果你也想和我们一起,翻译更多优质的 RxJS 文章以奉献给大家,请点击
这是一次尝试,使用 RxJS 来实现简单的无限滚动加载。
Angular 版本实现的文章:
什么是响应式编程?
简单来说,它使用异步数据流进行编程。这有一篇 所写的超棒文章及 egghead 提供的配套视频:
文章:
视频:
什么是 RxJS?
RxJS 或 Reactive Extensions 是最先由 Microsoft Open Technologies 开发的用于转换、组合和查询数据流的库。 (译者注: 这是 V4 版本的 RxJS)
这是 的演讲 ,非常棒。
下面是 对 Observables 和少数操作符的一些精彩介绍:
我们要做什么?
我们将要使用 observables 来开发一个简单的无限滚动加载。当用户滚动到指定容器高度的70%,我们就调用 API 从服务器获取更多的数据。我们会使用 来获取最新的新闻。
下面是我们将使用到的 RxJS 操作符:
- : 与数组的 map 类似,映射传入的数据流。
- : 与数组的 filter 类似,过滤传入的数据流。
- : 返回由当前发出值和前一个发出值组成的数组。
- : 返回的 observable 会在发出源 observable 的值之前先发出提供的值。
- : 只有当内部 observable 完成后,才会发出新的值。
jsbin.com 上的完整示例:
阶段一: 设置基础 html 和 css
导入 RxJS 库并使用 infinite-scroller
作为滚动容器的 id,获取的所有新闻都将追加到此容器中。
Naive Infinite Scroller - RxJS
#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
— 处理 返回的数据并增加当前页码。renderNews
— 接收每条新闻数据并将其渲染到页面中。
后面两个函数用来进行滚动计算:
isUserScrollingDown
— 检测用户是否向下滚动。isScrollExpectedPercent
— 检测用户是否已经滚动指定的百分比,从而加载更多数据。
阶段三: 设置 observable 流
/** 设置流**/const scrollElem = document.getElementById('infinite-scroller');const scrollEvent$ = Rx.Observable.fromEvent(scrollElem, 'scroll');复制代码
要捕获容器的滚动事件,我们需要创建滚动事件的 observable 。使用 就可以完成。在变量结尾处加 $
是一种惯例,以表示引用的是 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
$ 发出的滚动事件并将其映射成无限滚动加载逻辑所需要的值。我们只取滚动元素的三个属性: 、 和 .
将映射过的数据传给 pairwise
操作符,它会发出由当前值和前一个值组成的数组,如下图所示。
现在我们将这组位置数据传给 filter
操作符来根据条件进行过滤:
- 用户是否向下滚动
- 用户是否已经滚动到容器的70%高度
当 userScrollDown$
产生符合过滤条件的值后会调用 requestOnScroll$
。我们给了 requestOnScroll$
一个空数组作为初始值。
我们使用 来将 promise 转化成 observable 。fetch
发起 http 请求并返回 promise 。exhaustMap
会进行等待,直到 fetch 完成并且内部 observable 发出 API 返回的数据。
Observables 是懒加载的。这意味着除非订阅了它们,它们才会执行。我们订阅 requestOnScroll$
并传入 processData
作为订阅方法。当 exhaustMap
发出 API 的返回数据后,数据会传给 processData
,然后执行 renderNews
将数据渲染到页面中。
下面的 gif 图片实际演示了无限滚动加载的效果,注意观察右边的滚动条。
在我的下篇文章中,我将会尝试在 Angular 中创建一个无限滚动加载指令来实现它。
更新: 这是我下篇文章的链接