我们都曾遭遇过这样的烦恼:漫长的加载界面之后,却只等来了毫无反应的网页。随处可见的加载图标不停旋转,但一切似乎都停滞不前。让我为你描绘一个更生动的画面:
这种情况通常发生的原因是,网站试图在你一落地页面就 所有必要的数据。可能是正在处理某个API请求,或者多个API在顺序 数据,导致页面加载延迟。
。你可能会想:"这么大的公司怎么会不 用户体验?真是令人失望。"因此,用户往往会选择离开网站,这不仅影响了
这些重量级页面的数据,让用户一进入页面就能立即与之交互,会怎样呢?
refetching)概念的由来,也正是我们在这篇博文中将深入探讨的内容。
目录
预取技术:解决方案
针对上述问题,我们的目标是在页面加载至网站之前,就 该页面所需的数据,如此一来,用户在页面加载时便无需再次 数据。这种技术被称作预取。从技术层面来讲,其定义如下:
所需数据的方法,使主要组件无需等待数据即可加载,从而提升用户体验。
这可以改善用户体验,增强客户对你网站的信
更加以用户为中心。要实施预取,我们需要了解用户在网站上的行为。例如,最常访问的页面,或哪些组件在小交互(如悬停)时
应用预取技术。然而,作为开发人员,我们应该谨慎使用这个概念。过度预取也可能降低网站速度,因为你试图为未来场景 大量数据,这可能会阻塞主页面的数据
预取技术如何提升用户体验
让我们来看几个预取技术有益的场景:
这些都是在应用中实现预取的方法,它们有助于提升用户体验。
在本文中,我们将讨论最后一个场景:" "。这是一个预取技术能够带来 明显好处,为用户提供更流畅体验的典型
问题剖析
让我为你详细阐述这个问题。请想象以下场景:
现在,设想用户将鼠标悬停在该元素上,随后等待数据被预取并显示在弹出窗口中。在这段等待的时间里,用户会看到一个骨架加载器(Skeleton Loader)。
这个场景大致如下(此为动图,需要下载anigif.ocx控件观看):
每当用户将鼠标悬停在图片上时,他们必须等待很长时间,这真的很令人沮丧: (此为动图,需要下载anigif.ocx控件观看)
要解决此问题,有两种解决方案可供你参考,以帮助你着手并根据自身需求优化解决方案。
解决方案一:在父组件中预取数据
这个解决方案允许你在弹出窗口出现之前便已预取数据,而非在组件加载时预取。
当鼠标悬停在某个元素(如图片)上时,弹出窗口会显现。我们能够在这个元素的父组件上实现鼠标进入时预取数据。鉴于此,在实际需要悬停显示的组件(如图片)之前,我们就已经准备好弹出窗口所需的数据,并将这些数据传递给弹出窗口组件。
这种解决方案并不能完全消除加载状态,不过它能够显著降低用户看到加载状态的概率。(此为动图,需要下载anigif.ocx控件观看)
解决方案二:页面加载时预取数据
这个解决方案受到了类似x.com(可能是指Facebook的前身或类似的大型网站)的数据加载策略的启发,即在弹出窗口组件中,它们在主页面加载时部分预取数据,并在组件挂载时预取剩余的数据。(此为动图,需要下载anigif.ocx控件观看)
正如你从上面的动态图中所看到的,用户的个人资料详细信息在弹出窗口中查看。仔细观察,你会发现与关注者相关的详细信息是稍后预取的。
当你需要在弹出窗口中显示大量数据,但一次性预取所有数据可能对弹出窗口挂载或主页面加载造成较大负担时,采取这种技术就显得非常高效。
一个更好的解决方案是,在主页面上部分加载所需的数据,并在组件挂载时加载剩余的数据。
在这个例子中,我们在鼠标进入图片的父元素时预取了弹出窗口的数据。现在想象一下,一旦弹出窗口的数据加载完成,你还需要预取额外的详细信息。因此,基于上述x.com的方法,我们可以在弹出窗口加载时预取额外的数据。这样做的结果是:
在这里,我们采取以下步骤:
当鼠标进入图片的父组件时,我们预取渲染弹出窗口所必需的主要数据。
这给我们足够的时间来预取主要数据。
在弹出窗口加载时,我们预取另一组数据,即相册数量。当用户阅读姓名和邮箱等信息时,下一组数据已经准备就绪,随时可以展示。
通过这种方式,我们可以做一些小而巧妙的优化,最大限度地减少用户盯着屏幕上的加载动画发呆的时间。
React中如何实现预取
在本部分中,我们将简要介绍如何实现上述预取示例应用程序。
项目设置
要开始创建支持预取功能的应用,请按照以下步骤操作:
你可以使用 Vite.js(这是我使用的工具)或 Create React App 来创建你的应用
yarn create vite prefetch-example --template react-ts
当你用VS Code打开prefetch-example文件夹后,应该会看到如下的文件夹结构。
现在让我们深入了解一下我们将为这个应用构建的组件。
组件
在此示例中,我们将使用3个组件:
PopoverExample 组件
让我们从第一个组件开始,即PopoverExample。这个组件在界面上展示了一个图像头像(avatar),并在其右侧显示了一些文本。它的布局应该类似于这样:(此为动图,需要下载anigif.ocx控件观看)
该组件的目的是作为一个示例,模拟现实生活中的场景。在这个组件中,当用户将鼠标悬停在图片上时,会加载一个弹出窗口组件。
import { useState } from "react";import { useFloating, useHover, useInteractions } from "@floating-ui/react";import ContentLoader from "react-content-loader";import UserProfile from "./UserProfile";import UserProfileWithFetching from "./UserProfileWithFetching";export const MyLoader = () => ( <ContentLoader speed={2} width={340}height={84} viewBox="0 0 340 84" backgroundColor="#d1d1d1" foregroundColor="#fafafa" > <rect x="0" y="0" rx="3" ry="3" /> <rect x="76" y="0" rx="3" ry="3" /> <rect x="127" y="48" rx="3" ry="3" /> <rect x="187" y="48" rx="3" ry="3" /> <rect x="18" y="48" rx="3" ry="3" /> <rect x="0" y="71" rx="3" ry="3" /> <rect x="18" y="23" rx="3" ry="3" /> <rect x="166" y="23" rx="3" ry="3" /> </ContentLoader>);export default function PopoverExample() { const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const [data, setData] = useState({});const { refs, floatingStyles, context } = useFloating({ open: isOpen, onOpenChange: setIsOpen, placement: "top", });const hover = useHover(context);const { getReferenceProps, getFloatingProps } = useInteractions([hover]);const handleMouseEnter = () => { if (Object.keys(data).length === 0) { setIsLoading(true); fetch("https://jsonplaceholder.typicode.com/users/1") .then((resp) => resp.json()) .then((data) => { setData(data); setIsLoading(false); }); } };return ( <div style={{ display: "flex", flexDirection: "row", alignItems: "center", textAlign: "left", }} onMouseEnter={handleMouseEnter} > <span style={{ padding: "1rem", }} > <img ref={refs.setReference} {...getReferenceProps()} style={{ borderRadius: "50%", }} src="https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_5.png" /> </span> <p> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. </p> {isOpen && ( <div className="floating" ref={refs.setFloating} style={{ ...floatingStyles, backgroundColor: "white", color: "black", padding: "1rem", fontSize: "1rem", }} {...getFloatingProps()} > {isLoading ? ( <MyLoader /> ) : ( <UserProfile hasAdditionalDetails {...data} /> )} {/* <UserProfileWithFetching /> */} </div> )} </div> );}
程序运行的过程,让我逐步解释:
UserProfile 组件
既然我们已经定义了 Popover 示例,那么此刻便是深入探究 UserProfile 组件细节的时候了
这个组件出现在Popover组件内部。
为了演示预取示例,我们必须确保UserProfile组件仅作为展示组件存在;也就是说,它内部不包含任何
关于这个组件的关键点是,数据的 取发生在父组件PopoverExample中。在这个组件 ,当鼠标进入该组件(即触发mouseenter事件)时,我们开始 取数据。这是我们之前讨论过的解决方案#1。
这为用户在将鼠标悬停在图像上之前提供了 取数据。以下是相关代码:
import { useEffect, useState } from "react";import { MyLoader } from "./PopoverExample";export default function UserProfileWithFetching() { const [isLoading, setIsLoading] = useState(false); const [data, setData] = useState<Record<string, string>>({});useEffect(() => { setIsLoading(true); fetch("https://jsonplaceholder.typicode.com/users/1") .then((resp) => resp.json()) .then((data) => { setData(data); setIsLoading(false); }); }, []);if (isLoading) return <MyLoader />;return ( <div> <div>name: {data.name}</div> <div>email: {data.email}</div> <div>phone: {data.phone}</div> <div>website: {data.website}</div> </div> );}
此应用程序的完整代码可在
过度预取也可能导致性能下降
总结
了解到实现预取可以显著提升 的Web应用程序的速度和响应性,从而提高用户满意度。
为了进一步阅读,请参考以下文章:
原文标题: How to Boost Web Performance with Prefetching – Improve User Experience by Reducing Load Time ,作者:Keyur Paralkar