惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Forbes - Security
Forbes - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Palo Alto Networks Blog
Martin Fowler
Martin Fowler
T
Threatpost
D
Docker
S
Schneier on Security
M
MIT News - Artificial intelligence
G
Google Developers Blog
L
LINUX DO - 热门话题
J
Java Code Geeks
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
博客园 - Franky
C
Cyber Attacks, Cyber Crime and Cyber Security
K
Kaspersky official blog
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
V
Vulnerabilities – Threatpost
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
人人都是产品经理
人人都是产品经理
Spread Privacy
Spread Privacy
T
Tailwind CSS Blog
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42
C
CERT Recently Published Vulnerability Notes
The GitHub Blog
The GitHub Blog
Simon Willison's Weblog
Simon Willison's Weblog
NISL@THU
NISL@THU
MongoDB | Blog
MongoDB | Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Heimdal Security Blog
Recorded Future
Recorded Future
云风的 BLOG
云风的 BLOG
SecWiki News
SecWiki News
P
Privacy International News Feed
P
Proofpoint News Feed
O
OpenAI News
B
Blog
腾讯CDC
F
Full Disclosure
Apple Machine Learning Research
Apple Machine Learning Research
T
Tor Project blog
H
Hacker News: Front Page
Project Zero
Project Zero
Hugging Face - Blog
Hugging Face - Blog
C
Cisco Blogs
S
Security Affairs

BAIYUN'S BLOG

AI Coding 从小工到专家 - BAI YUN 电信 IPTV 内网融合教程:在局域网任意设备观看 IPTV - BAI YUN 美国住宅 IP 提供商体验对比 - BAI YUN Node.js 程序以 Keyless 模式从 1Password 读取密钥 - BAI YUN 高效优雅的观影指南 - BAI YUN 基于 Surge 的 DNS 优化指南 - BAI YUN RIME 鼠须管输入法简明使用教程 - BAI YUN 自动给不同 Git 项目设置不同的用户名邮箱 - BAI YUN Web3 新手如何选择加密货币钱包 - BAI YUN Web3 到底是什么? - BAI YUN 谨慎开启 Safari 的阻止跨站跟踪功能 - BAI YUN iOS Surge 开启后无法使用部分国产 APP 或网络出错的解决办法 - BAI YUN 屏蔽 Telegram 垃圾私信的有效办法 - BAI YUN 每个人都应该读一遍《1984》和《动物庄园》 - BAI YUN iOS 给常见机构号码增加联系人头像 - BAI YUN 在国内如何购买比特币和投资加密货币 - BAI YUN WEB 性能优化:如何让你的网站页面秒开(方法论) - BAI YUN 加密货币投资这件事 - BAI YUN 国内手机号注册推特教程 - BAI YUN 国内注册币安账号并通过 KYC 教程 - BAI YUN 使用一套助记词创建多个以太坊钱包账户 - BAI YUN Apple Watch 的几个用途 - BAI YUN 钱包安全指南:如何保护你的 NFT 和加密资产不被盗 - BAI YUN 我在 macOS 上高频使用的应用 - BAI YUN Web3 时代:如何使用 MetaMask 钱包实现一键登录 - BAI YUN 2022 年了 Safari 无法渲染渐变文字的 BUG 依旧存在 - BAI YUN 境外手机卡:澳门电信大湾区预付卡使用体验 - BAI YUN 解决 iCloud Drive 上传文件卡住的问题 - BAI YUN Electron 应用如何获取系统代理配置 - BAI YUN 解决 iOS 上 Google 账号登录会卡在加载状态的问题 - BAI YUN 记录 Github Copilot 惊讶到我的时刻 - BAI YUN 解决 Xbox 在国内下载速度慢 - BAI YUN 开始使用 IndexNow - BAI YUN Surge iOS 和 AmpliFi 的兼容性问题 - BAI YUN 自建图床指南 - BAI YUN 使用 TypeScript 提升可维护性 - BAI YUN 在 JavaScript 应用中正确的实现时区切换 - BAI YUN Node.js Tips - BAI YUN 使用 Dart Sass 代替 Node Sass - BAI YUN WSL 踩的坑和解决方法 - BAI YUN NPM & Yarn Tips - BAI YUN Git Tips - BAI YUN macOS 命令行 Tips - BAI YUN JavaScript 防劫持 - BAI YUN Dota2 - BAI YUN macOS 好用的系统维护工具 - BAI YUN 在 Linux 服务器上使用 Puppeteer 生成 PDF - BAI YUN The Golden Circle 黄金圆环法则 - BAI YUN CSS 写法进化史 - BAI YUN 一个 Web 开发者的 Windows10 开发环境设置心得 - BAI YUN Chrome DevTools Tips - BAI YUN React 设计模式和思想 - BAI YUN Linux & Unix 命令行环境的一些 Tips - BAI YUN 从传统 MVC 到同构:Web 发展历程 - BAI YUN Webpack 动态导入 - BAI YUN JavaScript 中的异步 - BAI YUN 关于我的博客 - BAI YUN
从 Loading 动画出发改进 React 应用的用户体验 - BAI YUN
baiyun · 2020-07-03 · via BAIYUN'S BLOG

在 Web 1.0 时代,用户在不同的页面之间跳转基本上都是通过全量刷新页面来实现的,这个时候 Web 开发者基本上不用自己添加 Loading 效果,浏览器会有自己的 Loading 状态。

不过进入 Web 2.0 时代就不一样了,因为这个时候 Web 开发者可以利用 Ajax 来实现页面的局部刷新,但是网站的用户通常是分布在不同的地理位置并且他们的网络环境差异也是非常大的,这就导致了同样的操作,有些用户可能马上可以看到结果反馈,有些用户就要等比较久的时间才能看到操作结果。

如果用户在页面上点击了一个按钮,但是等了很久页面都没有看到任何反馈,这个时候给用户的感觉就是页面卡死了,或者停止响应了,这种结果对用户的心理体验是非常不利的,所以这个时候就要加入一个 Loading 动画来即时的对用户操作进行响应,例如这种效果:

loading

通过增加 Loading 动画,可以避免让用户产生页面卡死或者没有响应的心理反应,不过 Loading 动画在某些场景下也会带来新的问题。

Loading 动画的问题

如前面所说,用户会分布在不同的地理位置,并且拥有不同的网络环境(比如 光纤、4G、3G),这就导致他们的网络延迟是截然不同的,如果一个网站部署在美国西海岸,在不考虑服务器响应速度的情况下,美国西海岸的用户从发起请求到收到响应大概需要 100ms 左右, 而中国用户访问这个网站,同样的请求因为网络延迟的关系就要 2s 左右,这个时间在恶劣的网络环境下会更久。

想象一个场景,现在网站上有一个文章标题列表,点击其中一篇文章的标题后会跳转到文章页面,按照上面所说,美国西海岸的用户点击之后只需 100ms 就可以看到新的内容,中国用户点击后至少需要 2s 以后才能看到新内容,如果在用户点击按钮之后立即显示 Loading 动画,对于那些网络延迟足够低的用户来说就会出现类似下面这样的场景:

flash-loading.gif

可以看到,Loading 动画一闪而过,给人的一个心理体验是非常不好的。

2021 更新,感受一下某国内热门 app 的 web 版

Loading 动画的展示时机

从实际的用户心理角度讲,打开一个页面如果三秒后还是 Loading 状态,这个时候用户就会产生不耐烦的感受,甚至一些用户在这种场景下可能会直接关闭页面而不会继续等待。

根据前面的情况,我们设定一个理想的 Loading 动画展示条件:

  • 如果在指定的时间内收到了服务器响应,那就不展示 Loading 动画,超过指定时间后才进行展示
  • 如果展示了 Loading 动画,那至少要展示足够长的时间,不能一闪而过

根据上面的条件,用 React 进行实现:

function Loading(props) {
  const { isLoading, children, delay, minDuration } = props
  const [visible, setVisible] = useState(false)
  const startTime = useRef(0)

  useEffect(() => {
    const remaining = minDuration - (Date.now() - startTime.current)
    const timeout = isLoading ? delay : remaining >= 0 ? remaining : 0

    const timer = setTimeout(() => {
      setVisible(isLoading)
      if (isLoading) {
        startTime.current = Date.now()
      } else {
        startTime.current = 0
      }
    }, timeout)

    return () => {
      clearTimeout(timer)
    }
  }, [isLoading])

  if (visible) {
    return <SkeletonScreen />
  }

  return children === undefined ? null : children
}

Loading.defaultProps = {
  // 用户触发异步操作后需要等 400ms 才会显示 Loading 动画
  delay: 400,
  // 如果展示了 Loading 动画,至少要展示 700ms
  minDuration: 700,
  // 异步操作是否正在进行中
  isLoading: false,
}

改进后的 Loading 效果:

可以看到改进之后,网络延迟足够低的情况下用户不会看到 Loading 动画,当网络延迟高的时候才会看到。

提升应用响应速度才是王中王

Loading 动画是把双刃剑,如果你的应用响应速度足够快,完全可以在大部分情况下不需要显示 Loading 动画。

本文从以下两个角度来说明如何提高 React 应用的响应速度:

1. 尽可能早的开始加载数据和代码

在 React 应用里常见的请求数据方式是这样实现的:

// pageA.jsx (这个文件会被异步加载)
function PageA(props) {
  const [data, setData] = useState(null)

  useEffect(() => {
    requestArticleData(props.id).then((res) => setData(res.data))
  }, [props.id])

  return <div>{data ? <Article {...data} /> : null}</div>
}

这种方式的问题在于,它需要等到 PageA.jsx 这个文件加载完成,并执行完上面的代码,等 React 组件挂载之后才会开始请求这个页面需要的数据,它是这样一个串行的过程:

  1. 用户请求切换到 PageA
  2. 开始下载 PageA 的代码
  3. 执行 PageA 的代码
  4. 开始请求数据
  5. 拿到数据,显示页面

有一种更好一点的方法是这样的:

  1. 用户请求切换到 PageA
  2. 开始下载 PageA 的代码
  3. 开始请求数据
  4. 执行 PageA 的代码,显示页面

要实现提前加载页面需要的数据,可以把加载数据的逻辑提取出来放在一个静态方法中,这样在用户请求切换到 PageA 的时候就可以通过这个静态方法来提前加载这个页面需要的数据,这种方法在 Next.js 这类框架中被广泛使用。

function PageA(props) {
  return <div>{props.data ? <Article {...props.data} /> : null}</div>
}

PageA.getInitialProps = async ({ query }) => {
  const data = await requestArticleData(query.id).then((res) => res.data)
  return {
    data,
  }
}

2. 合理使用缓存策略

在一些实际场景中,数据变化的频率是非常低的,这种情况在第一次通过网络请求到数据后可以缓存起来,后续再次访问这个资源的时候可以直接从缓存读取。

但是一旦使用了缓存,就要考虑很多额外的问题,比如缓存的 key、时效性,好在现在很多工具库(react-query、urql、react-apollo)都内置了一些开箱即用的缓存策略:

  1. cache-and-network (先返回缓存内容,再发起网络请求更新这个缓存,最后返回更新后的缓存)
  2. network-and-cache (优先使用网络请求,断网情况下使用缓存)
  3. network-only (只使用网络请求,不缓存内容)

具体使用什么策略需要根据具体的场景进行取舍,除了上面的这些工具库外,还可以看情况使用 Service Wroker,它也有类似上面这些缓存策略。

React Suspense & useTransition

React 一直在致力于打造更好的用户体验,在开启 React 并发模式之后可以通过 Suspense 和 useTransition 更简单直观的来处理异步状态,不过截止目前相关 API 还处于实验性阶段,如果你感兴趣可以查看 React 官方提供的文档了解更多信息 。

相关资源:

本文原载于:baiyun.me

原文链接:https://baiyun.me/improve-the-user-experience-for-react-apps