还记得吗?你是负责 Imager 的前端工程师,上次做了 Lazy Loading 改善了资源浪费的问题,公司对你的表现非常的满意。
但有干劲的你决定想想看,有没有其他方案也可以达到节约资源的效果,可以的话甚至可以和现在的 Lazy Loading 做结合!
一边品嚐着香醇浓郁的咖啡,一边滑着公司配给你的 MacBook Pro,一番寻寻觅觅之後,你找到了一个很酷的技术方案——
你稍微把文章扫过一遍,归纳出几个实作重点:
身为一名工程师,最快乐的时光就是动手写程序的时候啦。如果你喜欢直接看 Code 来理解的话可以直接前往完成的范例观看:
考量到 Lazy Loading 与 Infinite List 的结合较为复杂,这次会先实作只有 Infinite List 的版本,下一篇再将两者结合!
<InfiniteListTrigger />
首先我们需要一个 Element 搭配 Intersection Observer 来触发 Loading 的行为,也就是当这个 Element 出现在画面上时,就触发下载新的图片资料。
我们就将这个 Element 命名为 <InfiniteListTrigger />
,接着确保 Loading 状态时不会显示它,目的是避免重复触发下载图片列表。
// App.js
import React, { useRef } from "react";
...
import {
...
Loading,
InfiniteListTrigger
} from "./style";
const App = () => {
const triggerRef = useRef(null);
...
return (
<View>
...
{/* 并且为了防止重复触发图片下载,在 Loading 时我们就不显示触发用的 Element */}
{!isLoading && <InfiniteListTrigger ref={triggerRef} />}
</View>
);
};
export default App;
fetchNewImages()
接着我们来写一个模拟打 API 的 function,他做的事情很单纯,就是等一秒之後拿到新的图片列表。
// App.js
...
const App = () => {
...
const fetchNewImages = () =>
// 回传一个 Promise 是为了模拟打 API 时非同步的情境
new Promise((resolve) => {
const newImages = generateImages({ count: 10 });
// 我们假定每次打 API 平均一秒後拿到新的图片列表
setTimeout(() => {
setImages((prev) => [...prev, ...newImages]);
resolve();
}, 1000);
});
...
};
...
useInifiteList()
现在有了元件之後,我们需要一个 hook 将 <InifiniteListTrigger />
交给 Intersection Observer 监听。
这次要做的是相对简单,我们需要 ref 和 onView 来完成无限加载的行为,我们看看它们对应的工作:
<InfiniteListTrigger />
。// useInfiniteList.js
import { useState, useEffect } from "react";
const useInfiniteList = ({ ref, onView }) => {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
// 要监听的元件
const trigger = ref.current;
if (!trigger) return;
if (!onView) return;
// 这次我们的监听行为非常的简单
// 如果元件出现在画面上就触发 onView 事件
const callback = (entries) => {
entries.forEach(async (entry) => {
if (entry.isIntersecting) {
setIsLoading(true);
await onView();
setIsLoading(false);
}
});
};
// 开始监听元件
const observer = new IntersectionObserver(callback);
observer.observe(trigger);
// 别忘了取消监听元件哦!
return () => {
observer.unobserve(trigger);
};
}, [ref, isLoading, onView]);
return { isLoading };
};
export default useInfiniteList;
现在我们将上面的功能都组合在一起!
// App.js
import React, { useRef, useState } from "react";
import Image from "./components/Image";
import useCount from "./hooks/useCount";
import useInfiniteList from "./hooks/useInfiniteList";
import generateImages from "./utils/generateImages";
import {
View,
Title,
Loading,
ImageBlock,
ImageCount,
InfiniteListTrigger
} from "./style";
const App = () => {
const triggerRef = useRef(null);
const [images, setImages] = useState([]);
const { count, addCount } = useCount();
// 这里我们制作了一个下载图片列表的 function
// 当使用者看到 <InfiniteListTrigger /> 的时候就触发下载图片列表
const fetchNewImages = () =>
// 回传一个 Promise 是为了模拟打 API 时非同步的情境
new Promise((resolve) => {
const newImages = generateImages({ count: 10 });
// 我们假定每次打 API 平均一秒後拿到新的图片列表
setTimeout(() => {
setImages((prev) => [...prev, ...newImages]);
resolve();
}, 1000);
});
// ref : 监听的元件
// onView: 当元件出现在画面上时,执行此事件
const { isLoading } = useInfiniteList({
ref: triggerRef,
onView: fetchNewImages
});
return (
<View>
<ImageCount>图片载入数量:{count}</ImageCount>
<Title>Imager</Title>
<ImageBlock>
{images.map((image) => (
<Image key={image.id} src={image.src} onLoad={addCount} />
))}
</ImageBlock>
{/* 当我们在下载新的图片列表时,显示 Loading 让使用者知道图片正在下载 */}
{isLoading && <Loading>Loading...</Loading>}
{/* 并且为了防止重复触发图片下载,在 Loading 时我们就不显示触发用的 Element */}
{!isLoading && <InfiniteListTrigger ref={triggerRef} />}
</View>
);
};
export default App;
你埋首在程序之中也经过了好一段时间,现在可以来看看最终成果了!
看起来运作的很顺利,当页面滑到最下方之後,我们才开始下载下面 10 笔的资料。看到这样的成果,你不禁露出了骄傲的神情。
快乐的时光总是过得特别快,从肚子传出的咕噜声将你的注意力拉回现实,居然已经是下班时间了!你缓缓的从座位站起身子,拿着钱包准备去吃晚餐啦!
<<: day4 network simulator (雷)新不代表好
Canary Deployments 当要使用一部分用户测试生产中的新部署时,请使用 Canary ...
「这是个性很急,却也快不了的一整个世代。」 那,我们该改用什麽样的态度,跟年轻同事相处? 没有, 绝...
开头,先跟追踪此系列的读者道歉, 我失败了。 是的,我决定在这天为我的系列划下一个不是很好的句点,却...
这一篇与[day 26]的差异是,我们对於对於训练模型上参数的调整。 可以先看一下原始资料集,是之前...
将资料上传至Firebase上 使用UIImagePikerController来选取照片上传 使用...