写点什么

React 从入门到放弃,一个关于网页速度的故事

  • 2020-09-16
  • 本文字数:3261 字

    阅读完需:约 11 分钟

React从入门到放弃,一个关于网页速度的故事

AI 大模型超全落地场景&金融应用实践,8 月 16 - 19 日 FCon x AICon 大会联诀来袭、干货翻倍!

2011 年的时候,我碰巧找到一份编写Backbone.jsapp 的工作。如果你从没做过那样的工作,千万别去做。我会给那些愿意听我抱怨的人,喋喋不休地讲我遇到的各种困难。当我开始深入研究前端的替代方案时,我发现了FRPFlapjaxClojureScript。其中 ClojureScript 让我迷上了Clojure。我甚至针对 FRP 和 ClojureScript (以及Hoplon的前身 hlisp)做了一场成功的演讲

React

然后在 2013 年的时候,React 发布了。我在我的新工作中尝试了 React,并在 Clojure 主题的峰会(Clojure Cup 2013)期间发现 CLJS 和 React 简直是天作之合。React 为什么这么好呢?对于我来说,主要的卖点是它组件化做的非常好。


当你使用之前的东西,例如 jQuery、Backbone、Angular 或者其它东西,只需一年的开发之后,你的代码就会是一团事件监听器和触发器。千万别让我碰那些莫名其妙的 JS,jQuery 根本就不存在代码封装。哪个处理器在哪里绑定,用来做什么?很难说这是一个好的基础库!


然后我开始在Kasta工作,那里的 web 前端完全就是一团 jQuery 式的玩意儿。没人想要碰那些代码,因为你会花费数小时,甚至数天,来做一些最小的改动。然后 QA 会发现比你想象的多得多的错误状态。然后用户会向我们的呼叫中心报告更多的错误。那糟糕程度简直超乎你的想象。


因此,在进行了一些实验、测试和检查后,我决定使用 React + ClojureScript 技术栈,用 Clojure 进行服务器端渲染。

没落

有一段时间,一切看起来都很好。我们的有这样的架构:我们的组件在后端作为 Clojure 执行,所以没有服务器端 Node.js,太棒了!而且 UX 开发者通过出色的实时重新加载(多亏了 CLJS),能够从编辑器连接到浏览器的 REPL,并直接在编辑器试验。那简直太棒了!


长话短说,我们的前端变得越来越大。增量编译变得越来越慢——现在通常需要一到两秒以上。虽然我们做了一些尝试来保持整个 app 的性能,但最终我们还是失败了。这是一个痛苦的凌迟过程。应用程序变得太大,启动时间变得太长。服务端渲染只能帮助一部分,但是混合渲染会阻塞浏览器。在比较老旧的硬件或 Android 系统上,这变得不可接受!


2016 年的时候,一个主要原因是我们在启动时间上采取了大的改动,拥有了一个没有页面加载且具有大量交互的富 web 应用程序。在一段时间内,这是有效的!但是启动时间越来越长,导致在谷歌的 PageSpeed 上被评为可耻的 5/100(有时会达到 25/100 左右)。


除此之外,在执行下面描述的操作时,我们发现 React 也会导致一些有问题的实践。例如悬停用 JS(而不是用 CSS),下拉菜单用 JS,不渲染(在悬停时)隐藏的文本(谷歌对此会不高兴),奇怪的复杂逻辑等等。你可以创建一个没有这些问题的 React 应用程序,但是显然,你必须比我们有更好的自控能力(人无完人!)。


而且从那之后,我们的绝大部分用户都转向了移动 app。这使得 web app 成为新用户的主要入口。这意味着它的主要目标是为新用户快速渲染页面,因为想要更多功能的老用户现在都在移动 app 上了。而TTI(响应时间)在这里变得更加重要。

变革时刻

既然情况已经改变,那我们要怎么做呢?在 React 出现之前,我读过“我如何靠 vanilla JS 生存”这类的文章,但这些文章通常没有任何意义——它要么是一个关于它有多伟大的喧嚣,而无视所有问题(关注点分散、内聚性、组件化、代码封装),要么是一个(或几个)人在脑袋里想想而没有实际代码的项目。


早在二月的某个时候,我偶然发现了Intercooler.js。我不确定我以前是否见过它——也许我看过但一瞥而过——但这没关系。这一次,它引起了我的注意。


其理念是,所有的 HTML 都在服务器端渲染。而客户端根据元素的属性,更新部分 HTML。基本上类似 HTML+XHR。你不能任意妄为,但这是其重点之一:有些限制是好的,从而让你不会做一些疯狂的事情。你需要服务器的一些支持,这样你就可以渲染部分结果——虽然只是一个优化,但确实非常重要。


还有一个备选库——Unpoly。这个库围绕布局和样式有更多特性,但是在 XHR 上的想法比较少(例如,如果没有表单,很难完成带参数的 POST 请求)。而且这个库大的多。而且它是用 CoffeeScript 编写的,有许多类,这也有点问题


所以我用 Intercooler 对我们的目录页做了一个概念验证式的实现,而且它成功了!除了对 jQuery 有依赖和其它一些令人恼火的东西外…当我纠结于对 HTML 片段的请求时,我明白了一件事:当我为目录页选择技术路线图时,最后的选择是“类似 intercooler 的小东西”。


那为什么还不行动呢?

TwinSpark

我喜欢 Intercooler 在处理 AJAX 方面的流畅方案,所以我决定用一些汽车方面的东西来命名这个库,而 TwinSpark 似乎是一个不错的名字。这具体是个什么东西呢?


TwinSpark是一个用于声明式 HTML 增强的框架:你在元素上添加额外的属性,TwinSpark 对它们对一些处理。例如发起一个 AJAX 调用并用响应替换目标,或者增加一个样式类,或者,你们自己看看这些例子吧。


当然,它与 Intercooler 有些不同之处,不然它为什么会存在?最值得一提的就是,它不依赖 jQuery。它只支持现代浏览器(不支持 IE 和 Opera Mini)而抛弃掉了 88kb 的兼容性怪代码。


它还有以下优点:


  • 没有继承——这点再强调也不过分!

  • 指令有明确的扩展点

  • 支持对服务器的批量请求

  • 更严格的属性命名规范(这是我的观点,但ic-getic-post使我烦恼:别让我改变观点啊!)

  • 负载更小(多亏了没有 jQuery!)

  • 应该更快(也多亏了没有 jQuery)


老实说,最主要的原因是批处理无继承性。继承在这里尤其痛苦。在 Intercooler 中,如果你在 body 中声明ic-target属性,其中的所有标签都会认为它们的 target 也是这个。这样的话,在 HTML 树的某个地方有一个组件,而树上更高位置的一个属性改变了这个组件的行为。我认为这是一个奇怪的动态范围,我可不想要那样!:)


有趣的是,在对 TwinSpark 进行了一个月的尝试之后,Intercooler 的作者宣布它在做一个没有 jQuery 的现代版本:htmx。它有非常好的扩展点,而且可能会增加批处理…但是仍然有继承。:-(

TwinSpark 为什么是一个好主意

我们需要从两个方面看待它:它是否对开发者友好以及它是否对用户友好。React 对前者优化,而对于后者来说是非常讨厌的。


TwinSpark 方案在大部分情况下对用户更友好:更少的 JavaScript,更少的抖动,更常见的类似 HTML 的行为。在最差的情况下,我们将返回 2.5MB 简化后的(但没有 gzip 压缩过的)JS 和 700KB 的目录 HTML(其中一半是 React 的初始化数据)。JS 包那么大不是因为包含的图像、css 或其它一些复杂的东西,而是因为它是整个 app,有很多视图和逻辑。


现在它是 40KB 的简化的没有 gzip 压缩过的 JS(TwinSpark、分析埋点、一些行为代码和 IntersectionObserver 兼容包)和 350KB 的 HTML。两个数量级的差距,而且 HTML 也更小!


在开发者方面,我认为 React 仍然比较好些,但是 TwinSpark 比用 jQuery 在代码封装和组件化方面要好。另外还有很多方法来提升它。


好消息是开发流程没有太大变化!我们仍然编写从站点内存存储中查询必要数据(需要的时候发起一个 API 调用)的组件,但他们只在服务端执行。我们在我们以前的架构中有效地利用了后端,而且这使得我们能够完美地渲染“部分”HTML——因为组件不需要等待某个“controller”来给他们所有必需的数据。这也使得我们可以同时拥有 React 和非 React 版本,进行 A/B 测试,而无需编写二次标签。

结论

从首次尝试到发布,我们花了 4 个月时间。并不是我们刚开始时预想所需的时间(“应该最多需要两三周!”),呵呵,但并不是只有我们这么做。从代码中移除 React 相关代码并将我们的 app 打造成一个服务端应用程序仍然花费了很多时间和精力。它仍然需要一些润色,但我们还是决定发布它来缩短时间。A/B 测试显示我们是对的——特别是对于 Android 手机。


谷歌现在给我们的目录页排名 75/100 而不是 5/100。我想,这还是很不错的吧?:)


作者介绍:


Alexander Solovyov 现在是Kasta公司的首席产品官,之前是那里的 CTO。我们致力于为消费者和生厂商构建一站式电子商务平台。


原文链接:


A tale of webpage speed, or throwing away React


2020-09-16 10:022449

评论

发布
暂无评论
发现更多内容

滴滴前端一面高频vue面试题及答案

bb_xiaxia1998

Vue

TorchDynamo初探:Python ByteCode的动态修改

OneFlow

人工智能 深度学习 TorchDynamo

字节前端高频手写面试题(持续更新中)

helloworld1024fd

JavaScript

2023年工作上的几个小目标

SAP虾客

系统集成 在家办公 PRA 自动化仓库

面试官:你是怎样进行react组件代码复用的

beifeng1996

React

阿里前端二面必会react面试题总结

beifeng1996

React

2022年后疫情时代旅游业发展的新趋势

易观分析

旅游 后疫情时代

如何实现购物车一键全选?

Towify

vue组件通信6种方式总结(常问知识点)

bb_xiaxia1998

Vue

从源代码构建TensorFlow流程记录

京东科技开发者

tensorflow GitHub 开源 源代码 企业号 1 月 PK 榜

自己手写一个redux

helloworld1024fd

JavaScript

如何实现一个计算器

Towify

假如面试官问你Babel的原理该怎么回答

loveX001

JavaScript

字节前端必会react面试题

beifeng1996

React

vue组件通信方式有哪些?

bb_xiaxia1998

Vue

Java高手速成│实战:应用数据库和GUI开发产品销售管理软件(1)

TiAmo

JDBC GUI 数据库·

vivo 服务端监控体系建设实践

vivo互联网技术

云原生 监控 可用性 可观测

2023前端必会手写面试题整理

helloworld1024fd

JavaScript

那些高级前端是如何回答面试题的

loveX001

JavaScript

前端高频面试题集锦

loveX001

JavaScript

了解下 Rust 的所有权机制

ooooooh灰灰

rust 后端 编译

带你实现react源码的核心功能

flyzz177

React

王龙:数据烟囱亟需打破 —— 解读云原生数据库的2022

MatrixOrigin

数据库 云原生 MatrixOrigin MatrixOne

前端二面vue面试题(边面边更)

bb_xiaxia1998

Vue

坚果的2022年终总结

坚果

年终总结 OpenHarmony

面试官:能用JavaScript手写一个bind函数吗

helloworld1024fd

JavaScript

从React源码来学hooks是不是更香呢

flyzz177

React

无代码开发

间隔

令人头秃的js隐式转换面试题,你能做对吗

loveX001

JavaScript

面试官:说说React-SSR的原理

beifeng1996

React

从React源码角度看useCallback,useMemo,useContext

flyzz177

React

React从入门到放弃,一个关于网页速度的故事_架构_Alexander Solovyov_InfoQ精选文章