当前位置: 首页 > 科技观察

一款Facebook出品的JS框架--使用React.js和应用缓存构建快速同步应用程序

时间:2023-03-12 08:54:40 科技观察

Facebook出品的一个JS框架——使用React.js和应用缓存构建快速同步应用这两方面同样重要。倾向于主动使用缓存数据,这可能导致提供的数据过时;虽然倾向于及时获取最新数据,但可能会牺牲加载时间。当然,您可以吃蛋糕也可以吃,但它可能需要更多的硬件和/或更复杂的软件(意思是一个词:金钱)。取舍取决于具体的应用系统和业务需求。这篇文章是我们团队使用React.js和应用程序缓存来解决这个问题的一个例子。我们从哪里开始每次您在浏览器上打开标签时,标签都是发送慈善捐款的好理由。这是一件好事——事实上我们的慈善捐款只达到了100,000美元的里程碑——但是,我们有一个问题。我们的应用程序也太慢了。每个人都明白这一点。当用户切换到新选项卡时,他们需要速度和连续性。此外,我们没有声明:加载页面的延迟是人们关闭标签页的首要原因。我们希望我们的页面被接受并且有用。但是随着我们给页面添加了一些额外的功能,我们的页面加载问题就变得越来越突出。因为人们需要我们的APP快速提供内容信息。我们正在用Django的模板系统做一个交互式服务器来调用或提供页面。当用户处于快速网络环境中,并且我们的服务状态健康时,服务器响应时间约为65ms,这还算不错。但是,如果在您父母家*打开了一个选项卡,或者我们的数据库出现了短暂的暂停,则可能会给您的信任泼冷水。令人恼火的是,我应该承认我们构建的应用程序没有使用标准的前端框架,而只是使用了JQuery。考虑到我们的APP交互太多,太杂乱。我如何在各种代码类型上喜欢它。我们需要修改它。*我爱你们,爸爸妈妈!时代华纳有线,仅此而已。确定我们的需求当我们准备好解决问题时,我们必须决定哪些需要优先考虑,哪些需要放弃。以下是一些建议:页面必须快速加载。这是没有商量余地的。我们的页面必须是非本地URL。我们提高了VIA赞助广告的价格,在线广告需要验证其真实性以确保其安全。因为浏览器端的用户页面插件总是去掉我们的广告,这样网络广告只能使用http或者https协议。我们希望页面上的内容是最新的,但不一定是实时的。我们通过设备同步用户数据,保持最佳体验。我们以分页形式显示用户反馈;例如,我们显示新用户统计信息;我们有时也会运行捐赠设施,以滚动条的形式显示“筹集的资金”金额。虽然我们在一定程度上愿意接收稍旧的数据(就像在页面显示后提交数据),但理想情况下它发生在提交数据的那一刻。我们想减少前端混乱的代码。淘汰不属于优先级的代码是一件令人兴奋的事情***。让我们动手实践一下处理这些问题的想法。1.采用主动服务器端缓存我们曾经认为应该首先扩展服务器端缓存结构。目前,我们已经严重依赖Django的低级缓存(low-levelcache),这有助于实现我们的目标,但我们每次都必须添加它。每次都要写语句来判断缓存是否过期或者失效,我觉得这张幻灯片来自一个excellentpresentation(一个excellentpresentation)可以反映出Django在缓存问题上面临的挑战:另外,为了更好地受益于服务器端缓存,我们的缓存系统看起来是一个多层结构:(首先)每个用户一个完整的页面缓存,然后是用户数据的模块化缓存,(同时)每当数据发生变化时也需要智能判断数据是否无效。我们不想继续增加缓存系统的复杂性,因为我们在实施过程中遇到了一些与缓存相关的(系统)错误。更重要的是,还有网络传递差异的问题,比如对于一个新的TAB页面,在快速和慢速的互联网网络上性能有明显的差异,即使我们服务的响应时间降低到不到1毫秒,对于大多数用户来说,这个页面显示的速度不够快。不,那不行。二:在我们的页面上使用应用缓存“应用缓存?他不是个混蛋吗?”不,别这么无礼!……嗯,也许他是。在使用应用缓存之前,充分了解它的怪癖和陷阱是明智的。我们主要担心的是应用缓存会降低调试时的透明度,因为我们的服务器不会记录轻量级请求(我们将在接下来解决这个问题)。代码更改后的另一个与之前不同的小问题是更改应用于两个视图页面:需要一个页面提示浏览器获取资源,另一个页面使用新资源。不理想,但在我们的案例中可以接受。总的来说,我们的团队在应用缓存的限制方面遇到的麻烦相对较小;更多我们的应用程序不适用的情况会更容易解决。好吧,也许我们可以使用应用程序缓存。可能这是一种不需要经过大量重构的方法吗?我们快速而粗略的想法是使用Django来处理视图模板并返回一个html页面,以保持我们当前页面的完整性。当任何用户数据发生变化时,浏览器将从服务器和应用程序缓存中获取重新呈现的页面。我们的游戏计划:我们将激活当前页面上的应用程序缓存,因此它会绕过服务器加载。当用户对数据进行了一些更改,而我们想要持久化时,我们的页面会使用ajax请求将数据保存到数据库中,这通常是我们所做的。我们将从应用程序缓存列表中导入一个用户特定的版本号,因此对于每个用户来说,这个列表都是唯一的。当用户更新任何数据时,我们会创建一个新版本的用户应用缓存列表的内容,浏览器会知道并获取页面资源进行更新。在客户端另一方面,我们会检查应用程序缓存并在用户修改任何数据时更新它。浏览器会拿到用户的缓存列表,看到已经处理成新版本号的变化内容,重新抓取页面内容。理论一般来说,当用户下次浏览这个页面时,应用缓存会提供一个完美的页面在服务器端重新渲染。从好的方面来说,这些选项将引入非常小的工程投资。一个小缺点:此选项无效。浏览器获取资源的速度不够快是主要问题。如果你在新标签中修改了数据(比如在你的笔记中添加了一条笔记),然后在几秒钟内打开了一个新标签,应用缓存可能还没有获取到你修改的新页面,它仍然显示旧页面你没有添加注释的地方。从用户体验的角度来看,这就像数据丢失一样——即使是技术数据延迟,也是我们无法接受的。当涉及多个设备时,这个问题会变得更糟。如果您在设备A上更改了您的新标签页,然后在设备B上打开一个标签页,您可以保证获得旧数据。在随后的页面加载之前,您不会看到新数据。这不是很好。对不起,快速而粗略的选择。三:面向模板的本地存储和应用程序存储为了更简洁地做到这一点,我们可以使用应用程序缓存结合客户端模板将数据存储在本地。这看起来是一个不错的选择,除了应用程序缓存的“第二页加载”问题的坏情况,它非常快,并且它清理了我们的前端(重构......哇?)。作为奖励,我们的新标签页将在在线时被访问。出于几个原因,我们选择使用React.js作为模板。最主要的是我们有一些在其他领域使用应用程序的经验。我们还觉得学习曲线比Angular更浅一些,我们认真考虑了替代方案。说来也怪,长期以来构建前端框架一直在我们现有的jQuery之上,而我们被改变的数据更像是React中的“状态”,这将使我们更容易过渡到React。我们也选择了Facebook的Flux配置,因为我们认识到单向数据流可以让我们的代码更有逻辑性。Flux调度器也让我们更容易同步数据,这我将在下面描述。我们的React应用程序将从Flux存储中获取数据(1),后者将从本地存储中获取数据(2)。一旦安装了React应用程序,页面就会加载(3,4)。然后,我们的页面将对应用服务器进行Ajax调用(5),发送应用程序的所有数据——实际上,一个包含Flux存储的所有数据的对象。服务端接收到用户本地的所有数据,并与数据库中用户的数据进行对账(这将在下文“数据同步”中详细介绍),然后将最新的数据返回给应用程序(6).应用程序将从服务器接收最新数据并更新每个Flux存储(7,8)。一旦Fluxstore更新,store就会触发一个更改事件(3),并且React组件会更新它们的状态(4)。当用户更改某些内容时,将触发一个操作(11),更新存储的数据(7,8);当存储更新并触发更改时间时,我们会将数据持久化到本地存储(9),如果用户在线,我们会将数据持久化到数据库(10)。如果你对Flux有所了解,下图应该看起来很熟悉:(抱歉,看起来有点乱。)这个数据流图的重点是我们将快速向你展示新的标签页,并且在一秒钟左右的时间内,我们'将使用来自服务器的新数据更新您的页面。这些新数据可能包括您在另一台设备上对小部件所做的更改(如笔记),或者可能是慈善活动中“筹款”的实时统计数据。使用Flux配置最令人惊奇的事情之一是,通过调度程序的远程数据流与用户的操作在时间上完全同步。一致性使调试变得极其容易。因为存储是我们应用程序状态的真实来源,所以我们可以放心,当存储的数据被远程数据同步或用户输入更改时,应用程序仍然可以做出一致的响应。我们仍然可以在整个应用程序中保持单一的数据流,这使得代码简单得多。我们在应用程序速度和数据实时性能之间找到了理想的平衡点。数据同步我提到过我们会在每次页面加载时将你的数据与服务器同步。那么我们如何实现这个东西呢?我们给数据“块”的最后一次更新打上时间戳,然后在客户端(Fluxstore)和远程数据库中保存数据变化的时间Stamp(modified_at)来处理同步。例如,如果在你的一个笔记窗口中输入,它会将窗口的modified_at时间戳设置为现在,然后将你的笔记内容保存到本地存储,如果可能的话,Incorporation也会保存到远程数据库。然后,下次打开标签页时,我们会将窗口的相关数据发送给应用服务器,在应用服务器中,与窗口相关的客户端将时间戳与数据库中保存的时间戳进行比较,返回最新的数据。为简单起见,我们使用Flux存储对象来发送和接收数据。这使我们可以轻松地使用从应用程序发送的数据更新Flux存储,因为我们知道它将保持唯一性并与我们的存储具有相同的数据结构。我们目前的同步过程绝对不是完美的:如果发生同步冲突,我们只会选择最好的数据。对我们来说,这是一个可以接受的细节;毕竟,我们不是Evernote。即使这变得不可接受,以后也可以使用更智能的数据Merge和用户消息来解决。让我们跑得更快!(或者更确切地说是渲染)加载应用程序的缓存页面很好,但我们仍然必须运行React应用程序代码才能将其显示给用户,并更新所有组件渲染。对于相当大的应用程序,这可能需要数百毫秒甚至超过一秒。为了获得快速的首次加载体验,React提供了一个方便的renderToString方法,允许您先将DOM发送到浏览器(使页面首先出现),然后连接所有侦听器(使页面具有交互性)。这允许服务器端预渲染。在我们的例子中,我们想知道我们是否可以使用它在客户端使用——我们做到了。每次我们将数据持久化到本地存储时,我们也会让我们的React应用程序成为一个字符串,并将该字符串保存到本地存储。然后,在页面加载后,在我们做任何其他事情之前,我们从本地存储加载呈现的应用程序并将其放入HTML元素中。换句话说,页面只用3行JavaScript加载DOM!对于我们的应用程序For,预渲染将预加载时间减少了大约400毫秒。“地狱”:挑战和陷阱没有什么是完美的。重构的时候还有一些东西不是那么好玩。再见,从JQueryUI过渡到React的速度损失让我们放弃了几乎所有的JQueryUI组件,比如draggable。这有点烦人,我们花了一些时间来重新做我们以前做过的事情。尽管如此,事实证明我们仍然可以依靠不断增长的有用开源React组件列表来构建我们想要的东西。“为什么,renderToString,为什么?”另一个小的实现挑战:如果你使用过React的renderToString方法,你可能以前见过这个错误:Reactattemptedtousereusemarkupinacontainerbutthechecksumwasinvalid。当React在预渲染的DOM已经存在后渲染其应用程序时,它期望预渲染的DOM与将要渲染的DOM相同。这意味着你不能让Date.now()和Math.random()之类的东西影响你的DOM。要解决此问题,您可能需要花一些时间在差异编辑器中比较两个DOM字符串。不灵活的存储数据结构。我们设计应用程序和服务器返回的应用程序数据结构之间的不匹配。我们将新代码推送到生产环境后,您第一次加载的页面视图将包含从应用程序缓存中加载的旧应用程序代码。但是从应用服务器返回的同步数据会被结构化,我们的新版本会结构化。因此,如果在新版本中我们决定重命名或删除一条数据,并且您的页面将在第一次打开新选项卡时中断;旧的应用程序代码将不知道如何处理它。在打开下一个选项卡之前,您的浏览器服务器可能已经获取了最新的应用程序代码并将其放入应用程序缓存中,因此页面会正常运行。为了防止新标签页中断,我们需要维护一套可靠的内部API。那会有点痛苦。说到代码推送……如果我们搞砸了并破坏了应用程序,那么在我们修复它之前,每个看到损坏页面的用户都会看到一个额外的损坏页面。然后,应用程序缓存会经历烦人的双重重新加载更新。嘿伙计们,结局很好切换到React和Flux是一种乐趣。我们的团队发现自己重新爱上了前端开发,我们所做的更改使新工程师更容易进入代码库。在用户体验方面,我们的新标签一直在快速推进。对于网络条件较好的用户,该版本不会有太多改动;但是对于其他人来说,他们可以发现我们的应用程序不起作用找到使用它和喜欢它之间的区别。由于Tab需要通过横幅广告为慈善机构筹集资金,因此更快的页面加载会增加用户在离开我们页面之前看到的广告数量。这个版本增加了大约12%的广告印象(也有相应的资金收入)。当然,快应用不一定是好应用;只是一个好的应用程序不会成为一个即时令人讨厌的应用程序。对我们来说,它为未来提供了一个多么令人兴奋的基础。-----这不是很有趣吗?您想在一个有趣、充满活力的团队中工作吗?我们正在招聘!另外,如果你这周在附近的旧金山,我将在周五参加React.js推介聚会——如果你想谈谈,请告诉我。感谢TiZhao和JosiahGaskin对本文的评论。英文原文:UsingReact.jsandApplicationCacheforafast,syncedapp