Skip to content

Latest commit

 

History

History
322 lines (187 loc) · 19.2 KB

File metadata and controls

322 lines (187 loc) · 19.2 KB

十、应用外壳

我们的上一章是关于添加主屏幕安装和推送通知的——这两个功能都是为了通过添加功能来改善用户体验——但是,正如我们在本书开头的用户故事中所描述的,这个应用最重要的功能之一是它的包容性;这是一款适用于所有人、任何人、任何地方的聊天应用。

从 web 应用的角度来看,我们可以更好地将其重新表述为“任何连接,任何速度”。web 应用性能的最大障碍是网络请求:通过不良连接加载数据需要多长时间。

开发商可能无法对性能给予应有的关注,这仅仅是因为我们通常在城市中心的空调建筑内快速连接测试我们的场地。然而,对于 Chatastrophe 这样的全球应用,我们必须考虑欠发达国家的用户、农村地区的用户以及只有我们十分之一网络速度的用户。我们如何让应用为他们工作?

这一部分是关于性能的;具体来说,就是要优化我们的应用,使其即使在最恶劣的条件下也能表现良好。如果我们做得好,我们将有一个强大的用户体验,适合任何速度(或缺乏速度)。

在本章中,我们将介绍以下内容:

  • 什么是渐进增强
  • 铁路绩效模型
  • 使用 Chrome 开发工具来衡量性能
  • 将我们的应用外壳移出 React

什么是渐进增强?

渐进式强化是一个简单的想法,但后果很严重。它源于提供令人敬畏的用户体验的愿望,与对性能的需求相结合。如果我们所有的用户都有完美、超快的连接,我们就可以构建一个令人难以置信的应用。然而,如果我们所有的用户都有慢速连接,我们必须满足于一种更简单的体验。

渐进式增强说 porque 没有 los dos?为什么不能两者兼而有之?

我们的受众包括快速连接和慢速连接。我们应该同时为两者服务,并适当地为每一方服务,这意味着为最好的关系提供最好的体验,为较差的关系以及介于两者之间的一切提供更简单(但仍然很好)的体验。

一句话,渐进增强意味着随着用户连接的改善,我们的应用会越来越好,但它总是有用的。因此,我们的应用是与连接相适应的应用。

你可以想象这正是现代网页的加载方式。首先,我们加载 HTML——内容的基本、丑陋的框架。然后,我们添加 CSS 以使其美观。最后,我们加载 JavaScript,它包含了使其流行的所有好东西。换句话说,随着站点的加载,我们的应用会越来越好。

渐进式的增强模式促使我们重新组织网站的内容,以便尽快加载重要内容,然后再进行调整。所以,如果你在一个超高速连接上,你现在就得到了一切;否则,您只需要使用该应用就可以获得所需的内容,而其余的内容将稍后提供。

因此,在本章中,我们将优化应用以尽快启动。我们还将介绍许多工具,您可以使用这些工具关注性能并不断提高性能,但是我们如何衡量性能呢?我们可以使用什么标准来确保我们交付的是快速应用?输入轨道模型。

铁路模型

RAIL 是谷歌称之为“以用户为中心的性能模型”。这是一套衡量我们应用性能的准则。我们应该尽量避免偏离这些建议。

我们将使用 RAIL 的原则来加速我们的应用,并确保它对所有用户都有足够好的性能。你可以在阅读谷歌铁路上的完整文档 https://developers.google.com/web/fundamentals/performance/rail

RAIL 概述了应用生命周期中的四个特定时期。详情如下:

  • 回答
  • 动画
  • 闲置的
  • 负载

就我个人而言,我认为以相反的顺序来考虑它们更容易(因为这更符合它们的实际顺序),但这可能意味着说谎者,所以我们可以看出谷歌为什么回避这一点。不管怎样,这就是我们在这里讨论它们的方式。

负载

首先,您的应用加载(让它变得轻松!)。

RAIL 表示最佳装载时间为 1 秒(或更短)。这并不意味着您的整个应用在一秒钟内加载;这意味着用户可以在一秒钟内看到内容。他们感觉到当前任务(加载页面)正在以一种有意义的方式进行,而不是盯着一个空白的白色屏幕。正如我们将看到的,这说起来容易做起来难!

闲置的

一旦应用完成加载,它将处于空闲状态(并且在操作之间也将处于空闲状态),直到用户执行操作为止。

RAIL 认为,我们应该利用这段时间继续加载部分应用,而不是让你的应用坐在那里(懒惰!)。

我们将在下一章中进一步了解这一点,但如果我们的初始加载只是应用的基本版本,那么我们将在空闲时间加载其余部分(渐进增强!)。

动画

动画与我们的目的关系不大,但我们将在这里简要介绍它。本质上,如果动画不是以 60 fps 的速度执行,用户会注意到动画中的延迟。这将对感知性能(用户对应用速度的感觉)产生负面影响。

请注意,RAIL 还将滚动和触摸手势定义为动画,因此即使没有动画,如果滚动太慢,也会出现问题。

回答

最终(希望很快),用户执行一个操作。通常,这意味着单击按钮、键入或使用手势。一旦他们这样做,你有 100 毫秒的时间来提供一个响应,确认他们的行动;否则,用户会注意到并感到沮丧,可能会重试该操作,从而导致更多问题(我们都经历过这种情况——疯狂的双击和三次单击)。

请注意,如果您需要执行某些计算或网络请求,某些操作将需要更长的时间才能完成。您不需要在 100 毫秒内完成操作,但您必须提供一些响应;否则,正如梅金·卡尼(Meggin Kearney)所说,“行动和反应之间的联系被打破了。用户会注意到。”

时间线

正如前面的模型所示,应用必须在一定的时间限制内运行。这里有一个方便的参考:

  • 16ms:任何动画/滚动的每帧时间。

  • 100ms:对用户操作的响应。

  • 1000ms:在网页上显示内容。

  • 1000ms+:用户失去焦点。
  • 10000ms+:用户可能会放弃该页面。

如果您的应用按照这些规范运行,那么您的信誉良好(正如我们将看到的那样,这并不容易做到)。

使用时间线进行测量

在本节中,我们将了解如何使用 Chrome DevTools 评测应用的性能,这是我们用来跟踪应用加载和响应方式的几个工具中的第一个。

一旦我们对其性能有了概念,我们就可以根据铁路原则对其进行改进。

当然,开发工具总是在开发中,因此它们的外观可能与给定的屏幕截图有所不同。但是,核心功能应该保持不变,因此,密切关注工作原理很重要。

转到您在 Chrome 中部署的 Firebase 应用,并打开 DevTools 到 Performance 选项卡(我建议通过右上角的下拉菜单将工具卸载到单独的窗口中,因为有很多内容要查看);然后,刷新页面。页面加载完成后,您将看到类似以下内容:

这里发生了很多事情,让我们把它分解一下。我们将从 Summary 选项卡开始,底部的圆形图。

“摘要”选项卡

中间的数量是我们的应用完成加载所需的时间。你的号码应该与我的号码相似,根据你的上网速度有所不同。

到目前为止,这里最大的数字是脚本,它本身的速度接近 1000 毫秒。由于我们的应用使用大量 JavaScript,这是有意义的。马上,我们就可以看到大部分优化应该集中在哪里——尽可能快地启动脚本。

另一个重要的数字是空闲时间(几乎和脚本编写时间一样多)。稍后我们将看到为什么会有这么多,但请记住 RAIL 模型建议使用这段时间开始预加载尚未加载的应用的部分。到目前为止,我们从一开始就加载所有内容,启动所有内容,然后坐下来休息一会儿。只加载我们需要的内容(从而减少脚本编写时间),然后在后台加载其余内容(从而减少空闲时间),这将更有意义。

网络请求

我们现在将跳到网络请求,因为它将有助于解释性能概要的其余部分。

在这里,您可以看到在什么时候加载了什么数据。一开始,我们看到了很多设置文件:Firebase 应用和messaging库、我们的bundle.js以及页面的实际文档。

稍后,两个重要的调用是针对用户的:登录并加载用户详细信息。我们最后加载的是清单。

这种排序是有道理的。我们需要加载 Firebase 库和 JavaScript 以启动应用。一旦我们这样做了,我们就开始登录过程。

一旦我们的用户登录,接下来发生的事情就是我们从 Firebase 接收消息和数据。正如您将注意到的,这不会显示在图表上,因为它是通过 WebSocket 实时完成的,因此它本身不是一个网络请求。但是,它将出现在性能配置文件的其余部分中,因此请记住这一点。

瀑布

这里,我们有一个在渲染过程中任何时候 Chrome 实际执行的详细分解。

瀑布工具是详细而复杂的,所以我们必须满足于这里的表面一瞥。然而,我们可以从中得出两个见解。首先,我们可以看到所有的空闲时间可视化。大部分是在开始时,这是不可避免的,因为我们第一次加载文件,但在中间有很大的差距,我们可以尝试填补。

其次,您可以在最右边的瀑布部分看到应用何时从 Firebase 接收消息。如果您将鼠标悬停在每个块上,您实际上可以将接收消息的 Firebase 跟踪到我们的App.js,并将其状态设置为消息数组。

因此,虽然我们无法看到网络请求中加载的消息,但我们可以看到 JavaScript 执行中的响应。

截图

这是性能工具中我最喜欢的部分,因为它生动地说明了应用的加载方式。

正如前面所述,用户应该在加载应用后 1000 毫秒内看到内容。在这里,我们可以看到内容第一次出现在我们的应用上大约是 400 毫秒,所以我们看起来不错,但是随着我们的应用的增长(以及我们的脚本负担的增加),这种情况可能会改变,所以现在是时候尽可能地优化了。

PageSpeed 洞察

性能工具之所以伟大,是因为它们让我们能够深入了解应用加载的本质。我们将使用它们来跟踪我们的应用的性能,同时尝试改进它,但是,如果我们想要更具体、更详细的建议,我们可以求助于谷歌提供的工具PageSpeed Insights

转到 PageSpeed Insights(https://developers.google.com/speed/pagespeed/insights/ )并插入已部署应用的 URL。几秒钟后,您将得到关于我们可以改进 Chatastrophe 的建议:

正如您所看到的,我们的移动性能急需帮助。大多数见解都集中在我们的渲染块 JavaScript 和 CSS 上。我鼓励你通读他们对这些问题的描述,并尝试自己解决它们。在下一节中,我们将使用另一种进步的 Web 应用秘密武器——应用外壳模式,根据谷歌的规范改进我们的应用。

应用外壳模式

我们的应用的本质是消息列表和聊天框,用户可以在其中读写消息。

这个核心功能依赖于 JavaScript 来工作。我们无法回避这样一个事实,即在用户通过 Firebase 进行身份验证并加载 messages 数组之前,我们无法显示消息,但围绕这两部分的一切都是静态内容。在每个视图中都是一样的,它不依赖 JavaScript 工作:

我们可以将其称为应用外壳——围绕 JavaScript 驱动的功能核心的框架。

因为这个框架不依赖 JavaScript 来运行,所以我们实际上不需要等待 React 加载并启动所有 JavaScript,然后再显示它,这就是当前正在发生的事情。

现在,我们的 shell 是 React 代码的一部分,因此,在调用ReactDOM.render并将其显示在屏幕上之前,必须加载所有 JavaScript。

然而,在我们的应用和许多应用中,UI 的一个重要部分基本上只是 HTML 和 CSS。此外,如果我们的目标是减少感知的加载时间(用户认为加载我们的应用需要多长时间)并尽快在屏幕上显示内容,那么我们最好将 shell 保持为 HTML 和 CSS,也就是说,将其与 JavaScript 分离,这样我们就不必等待响应。

回到我们的性能工具,您可以看到,首先加载的是文档,或者我们的index.html

如果我们可以在index.html中移动 shell,它看起来会比现在快很多,因为它不必等待 bundle 加载。

然而,在我们开始之前,让我们拿一个基准来看看我们在哪里,以及这会有多大的改善。

使用您部署的应用,打开我们的性能工具并刷新应用(当 DevTools 打开时,使用可用的空缓存和硬重新加载选项,以确保没有意外缓存处于保留状态,然后按重新加载按钮访问它)。然后,查看该图像条并查看内容首次出现的时间:

一定要运行测试三次,然后取平均值。对我来说,平均需要 600 毫秒。这是我们的基准。

将 shell HTML 移出 React

让我们从定义我们想要进入index.html的内容开始。

在下图中,消息和聊天框之外的所有内容都是我们的应用外壳:

这就是我们想要摆脱 React 并转换为纯 HTML 的原因,但在继续之前,让我们先澄清一下。

我们的目标是创建一个快速加载版本的应用部分,这些部分不需要立即使用 JavaScript,但最终,我们的一些 shell 将需要 JavaScript。我们需要在标题中使用注销按钮,这将需要 JavaScript 才能正常工作(尽管只有在用户通过身份验证之后)。

所以,当我们谈论将这些内容移出 React 时,我们实际上要做的是拥有一个纯 HTML 和 CSS 版本的 shell,然后,当 React 初始化时,我们将用 React 版本替换它。

这种方法让我们两全其美:一个快速加载的基础版本,一旦 JS 准备好了,我们将替换掉它。如果这听起来很熟悉,你也可以称之为逐步增强我们的应用。

那么,我们如何管理这种交换呢?好的,让我们首先打开我们的index.html,看看我们的应用是如何初始化的:

关键是我们的div#root。正如我们在index.js中看到的,这就是我们注入 React 内容的地方:

现在,我们正在将 React 内容嵌入到一个空的div中,但让我们尝试一些东西;在其中添加一个<h1>

<div id="root">
  <h1>Hello</h1>
</div>

然后,重新加载应用:

<h1>会一直出现,直到我们的 React 准备就绪,在这种情况下它会被替换,因此我们可以在div#root中添加内容,当 React 准备就绪时它会被简单地覆盖;那是我们的钥匙。

让我们从App.js开始逐步移动内容,向下移动:

我们在这里需要的唯一一点 HTML(目前称为 JSX)是容器。让我们将其复制到div#root

<div id="root">
  <div id="container">
  </div>
</div>

然后,在ChatContainer(或LoginContainer、或UserContainer的内部,我们看到我们有一个div.inner-container,它也可以移动过来:

<div id="root">
  <div id="container">
    <div class="inner-container">
    </div>
  </div>
</div>

Note the change from className (for JSX) to class (for HTML).

然后,我们移动Header本身:

<div id="root">
  <div id="container">
     <div class="inner-container">
       <div id="Header">
         <img src="/img/icon.png" alt="logo" />
         <h1>Chatastrophe</h1>
       </div>
     </div>
  </div>
</div>

重新加载你的应用,在 React 加载之前,你会看到一个非常丑陋的 HTML 版本:

这是怎么回事?好吧,我们的 CSS 加载在我们的App.js中,在我们的导入语句中,所以在我们的 React 完成之前它还没有准备好。下一步是在index.html中移动相关 CSS。

将 CSS 移出 React

目前,我们的应用没有那么多 CSS,因此理论上,我们可以将整个样式表放在index.html中,而不是将其导入App.js,但随着我们的应用和 CSS 规模的增长,这将不是最优的。

我们最好的选择是内联相关的 CSS。我们首先在<head>上添加一个<style>标记,就在<title>标记的正下方。

然后,打开src/app.css,在/* Start initial styles *//* End Initial styles */注释中剪切(而不是复制)CSS。

将其放入样式标记中,然后重新加载应用:

应用看起来完全一样!这是个好消息;在这个阶段,加载时间可能没有明显的差异。但是,让我们重新部署并运行性能工具:

正如你所看到的,在加载指示器出现之前,shell(带有空白内部)看起来很好(反应应用已经启动它的标志)。这是用户通常花在盯着空白屏幕上的时间。

移动装载指示器

让我们向前迈出一小步,并将加载指示器添加到我们的应用外壳中,让用户了解正在发生的事情。

ChatContainer复制 JSX 并将其添加到我们的index.html。然后,重新加载页面:

<div id="root">
  <div id="container">
    <div class="inner-container">
      <div id="Header">
        <img src="/img/icon.png" alt="logo" />
        <h1>Chatastrophe</h1>
      </div>
      <div id="loading-container">
        <img src="/img/icon.png" alt="logo" id="loader"/>
      </div>
    </div>
  </div>
</div>

现在,用户清楚地感觉到应用正在加载,并且会更加宽容我们的应用的加载时间(尽管我们仍将尽最大努力减少加载时间)。

这是本章的基本原则:进步的网络应用要求我们尽可能地改善用户体验。有时,我们无法对加载时间做任何事情(在一天结束时,我们的 JavaScript 总是需要一些时间启动——一旦启动了,它就提供了良好的用户体验),但我们至少可以给用户一种进步感。

好的网页设计需要同理心。渐进式网络应用是指对每个人都有同情心,无论他们在什么条件下访问你的应用。

总结

在本章中,我们介绍了性能工具和概念的要点,从 RAIL 到 DevTools,再到 PageSpeed Insights。我们还使用 AppShell 模式显著提高了性能。在接下来的章节中,我们将继续改进我们的应用的性能。

我们的下一章将解决最大的性能障碍——我们庞大的 JavaScript 文件。我们将学习如何使用 React Router 的魔力将其分割成更小的块,以及如何在应用空闲时间加载这些块。让我们开始吧!