【Day17】数据展示元件 - Infinite scroll

   


元件介绍

Infinite scroll 能在面对多笔资料时,让卷轴滑动到底部时再载入下一页面的资料。

由於一次性向後端取得大批的资料,对於後端的资料计算、资料透过网路传输、页面的渲染,在效能上都有可能会有影响,因此将资料分批载入也有助於网页效能的优化。

另一个分批载入常见的做法是使用 Pagination,分页载入,虽然都是分批载入,但是使用情境有一些区别, Infinite scroll 的优点是一直往下滑就会自动有资料载入,操作效率较流畅,但缺点是难以回去找刚刚看过的东西。所以如果网页内容是希望让使用者能够有效率地找寻特定资讯时,这时选择 Pagination 会比 Infinite scroll 较为适合。

参考设计 & 属性分析

Infinite scroll 的特点是让资料滚到底部时自动载入,所以这边的关键是,我们要如何判断「是否已经滚动到底部」?

从上图可以知道,Element.scrollHeight 表示元件的可滚动范围;Element.scrollTop 指的是元素被向上滚动的高度,换句话说就是你已经走过的距离;最後 Element.clientHeight 就是指元素内部高度,也就是滚动可视范围的高度。

Element.scrollTop + Element.clientHeight >= Element.scrollHeight

所以「滚动到底部」换句话来说,就是你滚过的距离加上自己元素的高度,大於等於可滚动范围的高度。

介面设计

属性 说明 类型 默认值
height 元件高度 number
isLoading 载入中状态 boolean false
onScrollBottom 滑动到底部的 callback function
children 内容 list of ReactNode

元件实作

以下是我们想像当中的 InfiniteScroll,在元件的 children 当中就是被浏览的内容,所以被 InfiniteScroll 包起来的内容我们希望被不断的载入。

因为他是一个可被滑动的范围,所以这个容器需要被限制高度,内容超出这个高度才有办法被 scroll。

再来我们需要在滑动到底部的时候触发事件,例如需要去打某支 API 来载入资料,因此我们提供一个 onScrollBottom 的 callback。

然後我们在打 API 的时候,是一个非同步行为,会有载入中的状态,因此也有一个 isLoading 的 Boolean props:

<InfiniteScroll
  height={250}
  isLoading={isLoading}
  onScrollBottom={() => {}}
>
  {...}
</InfiniteScroll>

如下程序码所示,我们需要透过 useRef 让我们能够操作这个容器的 DOM,因为我们需要计算何时滑动到底部:

const infiniteScrollRef = useRef();

<InfiniteScrollWrapper
  ref={infiniteScrollRef}
  $height={height}
  onScroll={handleOnScroll}
>
  {children}
  {isLoading && <Loading />}
</InfiniteScrollWrapper>

前面分析我们也已经介绍过如何判断滑动到底部的方法,因此在 onScroll 的时候,我们需要去触发这个计算:

const handleOnScroll = () => {
  const containerElem = infiniteScrollRef.current;
  if (containerElem) {
    const scrollPos = containerElem.scrollTop + containerElem.clientHeight;
    const divHeight = containerElem.scrollHeight;
    
    // 滚过的距离加上自己元素的高度,大於等於可滚动范围的高度
    if ((scrollPos >= divHeight) && onScrollBottom) {
      onScrollBottom();
    }
  }
};

这样我们简单的 InfiniteScroll 就搞定了!
我们展示一下成果:

从上图来看,当我们滑动到底部的时候,就会去触发 GET api 来取得下一页的资料,并且将资料更新到画面上。

我使用的方式是,在 onScrollBottom 被呼叫的时候,表示他滑到底部了,所以我要取得下一页的资料,因此我透过 setPage 这个 useState 将 page + 1

const [page, setPage] = useState(1);

<InfiniteScroll
  height={250}
  isLoading={isLoading}
  onScrollBottom={() => {
    if (!isLoading) {
      setPage((prev) => prev + 1);
    }
  }}
>
  {
    dataSource.map(({ id, author, download_url }) => (
      <ListItem
        key={id}
        author={author}
        url={download_url}
      />
    ))
  }
</InfiniteScroll>

当 page 这个 state 被改变的时候,我就要去打 API 来载入资料,所以我这便是透过 useEffect 来实作,并且他的 comparison array 里面就放了 page,表示 page 被改变的时候,需要执行里面的内容:

useEffect(() => {
  setSideEffect({
    ...defaultSideEffect,
    isLoading: true,
  });

  fetch(`https://picsum.photos/v2/list?page=${page}&limit=${limit}`, {})
  .then((response) => {
     setSideEffect({
       ...defaultSideEffect,
       isLoaded: true,
     });
     return response.json();
		})
    .then((jsonData) => {
      setDataSource((prev) => [...prev, ...jsonData]);
    }).catch((error) => {
      setSideEffect({
        ...defaultSideEffect,
        error,
      });
    });
}, [page]);

InfiniteScroll 元件原始码:
Source code

Storybook:
InfiniteScroll

参考

https://codesandbox.io/s/yk7637p62z?file=/src/index.js

https://github.com/TimingJL/github-repos-search/blob/master/src/containers/MainPage/MainContent/index.tsx


免费美国电话号码 Google Voice 0月租

免费美国电话号码 Google Voice 0月租

使用Google Voice服务可以免费拨打美国或加拿大境内的任何电话,而国际电话(非北美地区)的费用也比传统电话便宜,比如拨打中国大陆的价格为1美分/分钟,香港的价格为2美分/分钟,台湾市话:2美...

国内注册公司和国外注册公司区别:注册国外公司好还是用国内公司好?

国内注册公司和国外注册公司区别:注册国外公司好还是用国内公司好?

国内公司、香港公司、美国公司是目前绝大部分跨境卖家的身份选择。国内公司身份自然不必多说,90%+都是。香港和美国公司则少的多。但选择的人多并不表示国内公司就是最佳的经营跨境电商身份选择。 国内公司 首...

网站打开速度慢怎么办?为什么网站一定要打开快

在深入探讨提高网站速度之前,让我们探讨一下为什么它对您的小型企业如此重要。 更快的网站意味着: 更好的用户体验 您的网站性能会影响用户的体验 - 当您的网站加载速度更快时,用户更有可能与之互动并花费更...

如何提高网站访问速度:怎么让网站更快打开的方法

网站速度优化对于创造积极的用户体验至关重要。 积极的用户体验是快乐用户的营销代言词。 快乐的用户访问您的网站并购买东西。 不满意的用户离开是因为他们厌倦了等待您的网站加载。 营销人员称之为“跳出率”...

网站服务器怎么选?如何选择最好的网站服务器

为什么网站速度很重要 到目前为止,您应该不需要说服网站速度对您的在线业务至关重要。 这是因为网站性能会影响您的品牌声誉、SEO 排名和转化率。 以下是发生这种情况的主要原因: 品牌口碑👍 老实说,当...

返回顶部