写点什么

我从“过时”的 React 开发中汲取经验教训

作者 | Josh

  • 2023-09-04
    北京
  • 本文字数:7639 字

    阅读完需:约 25 分钟

我从“过时”的 React 开发中汲取经验教训

不少开发者时至今日仍然觉得 React 就是前端的现代标准。所以在切入正题之前,咱们先给 React 的真实水平“祛祛魅”。

 

其实我这篇文章,灵感来自 Alex Russell 在 Mastodon 上发表的帖子:

 

今天有人问我,能不能在不需要支持 IE 的新应用中使用 React。

我想了半天,也找不到一个非得这么干的理由……

React 的落后程度,着实令人有些惊讶。

 

Alex 在帖子中提到,React 缺少对 Web 组件的支持能力。而且这不是新问题了,React 多年来一直没能补齐这块短板。没错,开发团队总说“已在路线规划当中”。但截至本文撰稿时,他们仍然没在具体实现或者预计发布日期上做出明确承诺。

 

与此同时,几乎所有 React 的同类产品(就是那些可以用来替代 React 的框架或技术),都已经把 Web 组件支持落地并投入了生产。

 

而且 Web 组件只是其一,这份“应该做到/应该做得更好”的清单上还有密密麻麻的更多条目。下面我就再简要介绍几条。

 

给 React“祛祛魅”

 

React 因为较早投身于框架标准的制定而受益匪浅,甚至在很多人眼中成了标准本身。但它在敏捷性和适应性方面存在严重缺陷。自从 2013 年左右开始,React 做出的每一项决策几乎都在加重技术债——而其他比较年轻的同类框架则早已摆脱了这些束缚。

 

再次引用 Alex 的发言:

 

React 相当于一直在针对 08 年的限制设计 13 年的技术。现在已经是 2023 年了,React 也彻底跟创新断绝了关系。事实上,它可以说是当前在实现函数式前端编程方面最为迟钝的方案……

 

React 已然老去,可能大多数人都没意识到它在变老、或者说已经老到了什么程序。所以我用另外一种方式解释给大家听:

 

React 设计至今,泰勒·斯威夫特已经出了七张专辑,而且那时候约翰·梅尔还跟詹妮弗·安妮斯顿是一对儿呢。(这七张专辑,甚至没算 Taylor’s Version 这张重制专辑。)

 

所以如果屏幕前的你正好也在过去几年间错把 React 当成了整个前端世界,那你可能错过、甚至完全没意识到自己失去了什么。毕竟大家用 React 已经太久,错把不必要的问题当成了现实。

 

而且尽管现代前端技术一直发展极快,但我们的思维似乎反而更僵化了,没有意识到从很多方面来说,React 为王的时代早已过去。(更严格地讲,因为大多数组织面对的实际需求从来就跟 Facebook 不一样,所以 React 它就没「为过王」。)

 

过去十年间,浏览器在 JavaScript 和 CSS 方面的新功能采用量一直急剧增长。技术和用户期望已经推到了这个位置,现有工具生态系统在迭代和适应原有 React 成果方面的表现,绝对远远超出大家的想象。至少在前端开发领域,越新的往往还真就是越好的,而传统软件已然跟不上潮流。

 

用惯 React,你所失去(或者根本没感受过)的那些美好

 

我们真的用不着再盲目追求庞大的生态系统了,毕竟现在生态共享已经为成主流。

 

每当有“未经证实”的新框架出现在我们的项目开发流程当中,大家关心的第一个问题似乎永远是:它的生态系统大不大、强不强?

 

甚至还没开始读这篇文章,很多朋友脑袋里已经出现了这个问题。不用 React,转投其他框架的怀抱?它的生态够大吗?

 

其实这是件很诡异的事,为什么我们会对生态系统的规模如此痴迷?

 

当然了,我们都不希望自己的框架用着用着就消失了,至少不能几年间就失去了更新和维护。这个完全合理。另外,我们也不可能把全副身家都押在那些太新、或者未经证实的技术上。但无论是 Vue、Svelte、Preact、Solid 还是 Astro,明显都远远超过了这个阶段。它们都有良好的支持和维护,所以对生态规模的过度追捧肯定还有更深层次的原因。

 

那症结究竟出在什么地方?我整理了一套自己的理论:

我们已经被“驯化”了,习惯于专门给自己待定的框架建包。

 

有经验的朋友可能知道,这种习惯最初源自 jQuery,但 React 的大热则使其成为通行标准。

 

在 React 这边,任何一个模块、小部件或者是库等等(包括 carousel、map 或者 accordion 之类)但凡要想发挥作用,就必须得专门针对 React 进行构建——常规的 Web 或者 JavaScript 要素是不行的。React 在状态处理和组件生命周期的规则上,就强制要求任何非明确为其编写的包或者库都有可能无法工作。

 

React 告诉我们,一切事物都需要专门为某个框架进行构建。但这其实已经没什么必要了,或者说本来就不应该是这样。

 

没错,我们压根不该这么做。毕竟你 React 不是总宣称自己是“纯 JavaScript”框架吗?既然是纯 JavaScript,那就应该能跟一切纯 JavaScript 要素协同运作才对。

 

当然,其他前端框架也难免会有自己在状态、架构方面的一些规则和惯例,我们偶尔也会掉进它们挖的坑里。我承认,就算是 Svelte 或者 Vue 之类,也或多或少要做一点针对性的构建和调整。

但最大的问题是,这里我要明确强调这一点:

还没有其他哪种现代前端框架,会像 React 这样顽固地表现出跟平台间的不兼容。

 

如果大家正在使用其他现代工具和框架进行构建,那市面上可用的普通 JavaScript 包大概率已经能切实满足你的需求,而且这样的包可是以成千上万计。它们几乎不会导致渲染周期或者其他特定于框架的问题,而且都提供使用 Web 组件的选项。

 

也就是说,我们通常用不着为自己的项目定制专门的包或者库,因为需要使用的这些要素很可能已经跟平台兼容了。这样的开发过程才叫顺畅、才叫丝滑。

 

Preact Signals 就是个典型例子:虽然它是为 Preact 而构建的,但却能在任意框架中导入和使用,甚至连普通 JavaScript 也不例外。Web 组件也是,几乎跟一切除 React 以外的现代框架相兼容。

 

当框架有所欠缺的时候,平台往往也能出手补齐短板(比如说表单提交,这在 React 中一直是个痛点。但现在通过双向数字绑定加浏览器提供的约定,整个实际已经相当轻松。)

 

最糟糕的是,哪怕是需要做额外构建的情况,其实施难度也要远低于 React。(至少不需要把 useState 跟其他框架的版本比来比去。)

 

对于思想保守的开发者来说,在项目中使用新工具、新成果往往不是啥好事,他们会非常谨慎地尝试那些尚未经过全面验证的东西。但请大家务必牢记,新事物也有自己的优势,比方说技术债更少、直接放弃对陈旧浏览器的支持等。此外,新产品还能更自由地放飞新灵感、在更加现代的浏览器功能之上进一步迭代。

React hooks 其实有点过时了


Hooks 是 React 的最新发展成果,用以取代之前的类组件。

 

必须承认:hooks 代表着前端领域的巨大转变。它们彻底改变了我们在应用程序中的逻辑和状态组合方式。另外 hooks 也的确非常棒,几乎每个框架都围绕着类似 hooks 的模型来实现状态管理。

 

但 React hooks 这东西真的不新鲜了。实际上,React 的稳定 hooks 几乎跟我们家孩子一样大,而这小子再有几周就该上学前班了。

 

可以说 hooks 已经不算是什么竞争优势,甚至不再是什么功能亮点——其已经成为一种基准,成了我们最常规的开发方式。

 

其他框架不单也有自己的 hooks 实现 ,而且更重要的是:它们要么更快、要么更智能、要么更易于编写,甚至三点兼而有之。

 

Preact 的 Signals 相当出彩;Svelte 超级简单的跨组件状态共享机制 store 也很棒。Solid 也有 Signals,甚至 Vue 3 的 composition API 也受到了 hooks 的直接启发。而且相较于 React 的实现 ,它们都有自己的一些核心优势。

 

Hooks 模式值得表扬,React 也对它的全面普及起到了重要作用。但几乎每种其他框架都要做得更好、规则更少、而且不那么依赖样板。

 

如果大家不太熟悉 Signals 的概念,这里简单介绍一下:粗暴总结的话,我们可以把它看作 reactive State 的一种迭代演进;是种对 hooks 的更新,能以更好的默认项处理重新渲染。Signals 不再重新渲染整个组件,而只处理需要重新渲染的节点。

渲染早就不需要微观管理了


首先我得承认:我并不确定 useMemo 和 useCallback 之间到底有什么区别,也不知道什么时候该用、什么时候不该用。真的,哪怕我在写文章之前认真读了不少相关帖子,还是没太搞清楚。

 

还有另外一点:我仍然不清楚什么该用 useEffect 依赖数组,什么时候不该用,也不知道为什么要用。我感觉每写入一个 useEffect 调用,我都得花 15 分钟重构代码,让它符合 linter 格式。哪怕有 99%的时候它都能运行良好,我也不想被那 1%的几率拖入无底深渊。

 

我敢打赌,如果大家用过 React,那肯定也会跟我有类似的感受。也许你已经接受了这种模糊性和玄学意味,并且觉得现实就是这样。但我真心想要提醒大家:

在其他框架里,我们已经有很多年不用对渲染周期做这种微观管理了。

 

现在的框架已经足够聪明,完全可以自己搞定这些问题。而不需要你牵扯着它的手,一步步解释它们应该做什么。

 

现代框架们也都知道,如果没有必要,就别把宝贵的资源浪费在重新渲染上。它们很聪明,知道只需要更新值,而不是不断重新评估那些根本不需要的东西。

 

……当然,它们也并不完美,有时候也会犯错。但至少在知道要做什么、还有在默认情况下如何高性能地达成目标这件事上,它们做得普遍比 React 要好得多。

 

其他框架上也有需要优化的部分,但这种优化需求跟 React 相比简直就是小巫见大巫。

其他框架的 useEffect 版本用起来也更友好


当我们希望组件在进入 DOM 时做点什么,且/或希望它能根据其他数据或变量以动态方式重做某些计算时,几乎所有 其他框架都有比 React 上 useEffect 更好的办法。

 

关于这个问题,应该用不着我多费口舌了。毕竟在 React 社区之内,useEffect 也是出了名的危险,甚至很多老手建议彻底别用。总之请相信我,除了 React 以外,没有哪种其他前端框架会让人们如此害怕使用一项正常、有用的功能,也没有哪种框架会以如此迟钝的节奏处理这个致命问题。

 

真的没人会为了在安装组件时实现一点点功能,就去费力寻找第三方包——这纯属是没事找事。

扩展已不再是前端关注的重点


每当有比 React 更新的框架出现时,人们总是爱问:它的扩展性咋样?其实这个问题,如今也没啥必要了。

 

首先要强调一点:当初 React 诞生的那个时代,面对的现实问题跟现在不同。

 

在那个时代,大多数前端 UI 都是用原生 JavaScript 或者 jQuery(之类)构建的。而现在我们知道,这种应用构建方式确实无法很好地扩展到特定范围之外。

 

这是因为我们必须为每个要素、每个 DOM 节点都编写相应的选择器,还必须自己手动跟踪和同步状态,而这往往涉及混乱且极易出错的 DOM 写入和读取。更要命的是,这些操作的速度也很慢(因此才会出现虚拟 DOM,虽然它也已经过时很多年了)。

 

在那个时候,编写模块化代码几乎是不可能的,JS 文件经常会膨胀到几百甚至好几千行。如果有多个开发者在同一项目上工作,那他们经常会重写、重复甚至覆盖掉彼此的代码(部分原因是代码经常会进入共享的全局命名空间,因此有可能发生冲突)。你的应用越大、越复杂(比如像 Facebook 那样),那冲突问题就越严重。

 

所以一条铁律被深深刻进开发者的骨髓,这也成了前端“可扩展性”的基准:必须保证即使应用规模呈现出指数级增长,也仍然保有合理的可维护性。

 

总之,担心前端框架无法扩展是种跟 jQuery 一样古老的思维惯性,属于现代 Web 开发道路上已经过时的陈旧观念。

 

React 确实解决了很多问题,但它并不是现代工程学的奇迹,而只是想出了一种管理和共享状态的好办法。它让数据有了响应性,把复杂性抽象出来,并帮助开发者在不引发冲突、命名空间冲突或覆盖的前提下,得以共享相同的编程模式。

 

React 绝不是前端可扩展性方面最好、唯一甚至是最早的解决方案。相反,它只能说是同一范式下多种可行的方案版本之一。(也是最古老的方案之一。)

 

大家可能会问,你怎么就敢言之凿凿?因为有人运行了大量基准测试并公开了结果,把 React 的性能跟其他所有前端框架进行了大规模比较。(这里我就不贴链接了,毕竟资料在网上到处都是。)所有研究都证实,前端领域中几乎所有其他框架选项都比 React 表现更好,多数情况下甚至可以说是好得多。

 

这里,我指的是一般意义上的可扩展性,包括将复杂度控制在最低水平,而且不会随应用体量的提升而线性增长。当然,也有些框架在特定场景下的可扩展性更好或者更差,比如用 Markdown 文件构建静态 HTML、或者其他更加具体的任务方面。这个就要具体情况具体分析了。

服务器端渲染不再需要特殊对待


之前,我曾经错误地把服务器端渲染跟 React 服务器组件搞混了(但考虑到它令人困惑的命名约定,出点小偏差也在情理之中……对吧?)。

 

几年之前,React 几乎是唯一能实现服务器内容渲染的框架(主要通过 Next.js 实现)。当时,人们对 React 可以在服务器上作为 HTML 渲染的想法莫名兴奋,因为这全面颠覆了客户端单页应用(SPA)的通行标准。服务器端渲染带来了不可忽视的速度与 SEO 提升,所以在一段时间里,React 相较其他框架形成了领先优势。

 

但大家肯定也猜得到:最早的版本几乎不可能是最好的版本。

 

SvelteKit 默认在服务器端渲染,大家不用做任何额外操作,其中还提供对渲染模式的细粒度控制选项。

 

Resh(Deno 的前端框架)也是全服务器渲染的,只有特别指定的“孤岛”(island)才会在客户端渲染,其他一切均仅作为静态 HTML 发布。Fresh 还用到了 Preact(比 React 速度更快,有 Signals,外加性能更好、更符合直觉使用习惯的 useState 版本和响应式模型)。

 

Astro 也支持服务器渲染,允许开发者在服务器端渲染需要的任何组件。它对其他框架的组件也有很好的渲染效果,某些情况下甚至可以作为 Next 的性能升级选项。

 

SolidStart(Solid 的元框架)提供服务器渲染功能,Qwik 就是完全围绕这个中心构建而来。甚至 Ember 和 Angular 等比较陈旧的框架也有类似功能,这里就不一一列举了。

 

重点在于:以往,React 确实是少数几种能在服务器端渲染客户端视图框架组件的方案之一。但如今,服务器渲染早已成为桌面平台的主流。许多新兴框架不仅提供服务器端渲染选项,甚至还把它当成默认设置。

 

PHP 又回来了,朋友们。

双向数据绑定并不困难,效果也不错


我还想再强调一点:React 是 Facebook 开发出来的,为的是解决 Facebook 面对的独特问题。

 

React 最强烈的倾向性之一,就是认为数据应该只以一种方式(自上而下)流动。从这个角度,也能看出 Facebook 在 2010 年代早期所面对的现实问题,如何塑造了 React 的架构与基因。

 

有那么一段时间,人们甚至把单向数据流视为最佳实践。但现在,我们已经找到了解决双向数据绑定缺陷的解决方案,并意识到在多数情况下,双向数据绑定实际更加方便。

 

在 React 中处理表单是出了名的麻烦,因为用户的每一次键盘输入都对应两个步骤:从输入中获取值,之后设置状态来匹配这个值(这反过来又会对输入进行毫无必要的重新渲染,以此包含已经获取到的实际值,并跟 React 状态保持同步)。所以虽然大家在使用时可能察觉不到,但麻烦是真的麻烦。

 

Svelte、Vue 等许多其他框架就没这个问题,我们可以使用绑定状态的方式让它在两端自动更新。如果状态改变,那么 DOM 就会更新,而 DOM 的改变反过来也会触发状态的更新。

 

这样我们就不用像杂耍一样在各个步骤间反复横跳了。比如说,当我们想要捕捉某个文本框的值,就可以做双向数据绑定。之后当用户在字段中输入时,数据会自动更新,我们随时可以获取、无需借助任何额外步骤。如果在此期间还需要做点其他操作,比如设置一个值或者清除字段,那也是轻松加愉快。

 

双向数据绑定能够让数据和 DOM 保持同步,消除了不断确认二者同步的复杂步骤。

 

但双向数据绑定有没有短板?当然有,而且我发现最佳实践背后的种种僵化局限,几乎足以把好处消弭殆尽、甚至还不止。所以只要可以,还是尽量用单向数据流为好。

其实样式可以很简单


如果大家主要使用 React,那在前端组件中的样式处理上可能已然经历过两、三次迭代。

 

比如说,我们之前会直接把.css 文件导入 JSX 组件,或者使用 CSS 模块、样式组合和/或 Tailwind(可能使用 classnames 或者 tailwind-merge 包,再辅以额外的 Tailwind 插件)。而这些,还仅仅只是比较主流的办法。

 

Tailwind 也有自己的一团乱麻(我其实并不太喜欢它自带的前端框架;我觉得它违背了平台的本质,可能为了追求短期方便而损害长期利益)。但无论如何,这些样式解决方案的存在终究是好事,给了开发者更多选择。相比之下,React 那边打一开始就没提供过任何官方认可的样式选项。

 

很多朋友可能没发觉,样式在其他框架里早已不再是问题。

 

具体来讲,Vue 和 Svelte 都有自己的组件样式。它们都有组件级别的范围(Vue 是 opt-in,Svelte 则是 opt-out)。它们都跟原始 CSS 配合得不错,而且跟其他前端框架一样也都能跟 CSS 模块、Tailwind、Sass 或者其他你喜欢的方案良好兼容。

 

但最重要的是:任何 CSS 可能出现的问题(不管大家是否真的认为这是问题),都完全由内置的样式方案解决了。我们用不着面对一大堆包和配置,毕竟 scoped CSS 可以搞定你能想到的所有需求。

 

严格来讲,在看了这么多 CSS 不好的理由之后(其实不至于,但不太擅长 CSS 的开发者总喜欢这么说),我们对 CSS 的任何不满其实已经被 scoped styling 解决了。而且,除 React 之外的很多框架都已经 内置了这项功能。

新框架,已经没那么难学了


我猜测,很多如今熟悉了 React 的朋友应该还记得当初学习时的痛苦情景,而且觉得其他框架肯定也是这么难以上手。这样的顾虑阻碍了我们探索新事物的脚步——绝对很难,毕竟这可是第一次接触……

 

具体包括状态管理、props、嵌套、组件生命周期、hooks,还有如何编写 JSX 的诸多细节等等……即使是最狂热的 React 粉丝恐怕也得承认,对初学者来说这些都不是容易快速掌握的知识。(别嘴硬,当初大家明明都学得很费劲。)

 

但别担心,我给大家带来了个好消息:其实没有哪款工具像 React 那么难学,而且只要掌握了其中一种框架,往往也能快速上手其他框架方案。

 

我喜欢把接触新框架比喻成学习第二种乐器。第一次学音乐时,我们得了解关于乐理的各种知识,之后才能拿起乐器尝试让它出点动静。但在学习第二种乐器时,前面那些铺垫部分都可以直接跳过,所有概念已经了然于胸。你已经明白音乐是怎么回事,唯一要做的就是把原本的肌肉记忆稍微调整、转化成另外一种新的形式。

 

前端开发也差不多:每种框架都有组件,它们都跟 TypeScript 相兼容,也都有 props、children 和 reactive state 等概念。这些都是我们习以为常而且喜欢使用的技术成果,只是在不同框架上的具体实现各有区别。

 

说到这点:虽然 React 无疑助推了这些概念的落地,但给出的所谓“最佳实现”却相当笨拙、愚蠢。

 

伟大的事物都是通过迭代逐步完善出来的。所以在大多数情况下,后出现的前端框架自然在继承 React 核心思想的同时,迭代出了明确的比较优势。

 

也就是说,React 有点像一个落后于主版本的 git 分支。如果大家长时间只盯着 React、围着它打转,那很可能意识不到这一点。但现实在这里,前端开发已经整体迈进了一步。生态系统也接纳了这些想法,并在匹配之下让整个开发体验都上了个台阶。

 

我们现在不缺少性能更高、难度更低、学习曲线更友好的选项。或者说,大家连 React 都能啃下来,那其他框架根本不在话下。

 

如果 React 真的已经过时,那有什么靠谱的替代方案吗?请持续关注,下一篇我们会推荐一些“值得一试的其他选项”。

 

参与链接:


https://joshcollinsworth.com/blog/antiquated-react#part-2-things-you-forgot-or-never-knew-because-of-react


相关阅读:


React JS 广受业界认可,高级开发者年薪百万

从新 React 文档看未来 Web 的开发趋势

我被 React 劫持了,很痛苦又离不开

React 开发者们的 Solid.js 快速入门教程

2023-09-04 08:003524

评论 1 条评论

发布
用户头像
一句话,学不下去就去和Vue一桌吃残羹剩饭去~
2023-12-17 15:43 · 山东
回复
没有更多了
发现更多内容

6个JDK自带JVM调优工具,一次性打包给你说清楚

田维常

jvm调优

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

鄙人薛某

Java 并发编程 AQS 并发 ReentrantLock

区块链交易所软件,数字货币场外交易系统搭建

13530558032

这4个Java异常框架,很多人竟然还不知道

比伯

Java 编程 架构 面试 计算机

接口测试并不只是测试参数和返回值

测试人生路

接口测试

区块链IM即时社交通讯系统开发,区块链社交平台源码搭建

13530558032

K3d vs Kind 谁更适合本地研发

郭旭东

Kubernetes k3s kind

7面阿里,最后一面居然挂在了JVM上面!狠下决心恶补JVM知识

Java架构之路

Java 程序员 架构 面试 编程语言

微服务架构中的“参天大树”:SpringBoot+SpringCloud+Docker

小Q

Java 学习 容器 面试 微服务

为什么容器内存占用居高不下,频频 OOM

996小迁

Java 架构 容器 面试 k8s

go-zero 如何扛住流量冲击(一)

万俊峰Kevin

microservice go-zero goctl Go 语言

C++调用Go方法的字符串传递问题及解决方案

华为云开发者联盟

c++ 内存 代码

奋力准备一个月成功进字节,来看一下我都看了哪些资料做了哪些准备

小Q

学习 编程 程序员 架构 面试

来不及解释了,快上车!LR.NET开发平台助力企业信息互联

雯雯写代码

解读登录双因子认证(MFA)特性背后的TOTP原理

华为云开发者联盟

算法 totp 密钥

渣本全力以赴33天,四面阿里妈妈(淘宝联盟),拿下实习岗offer

小Q

Java 学习 编程 架构 面试

【应用运维】公司业务迭代迅速,运维如何高效进行应用发布?

嘉为蓝鲸

可视化 PaaS 运维自动化 部署与维护 发布

深圳区块链钱包系统开发,区块链钱包app源码

13530558032

为什么有的专科程序员比本科程序员薪资高?他们之间有多大的区别?

Java架构师迁哥

备战2021年金三银四,阿里P8面试官梳理的2020年999道大厂高频Java面试题(附答案)

Java架构之路

Java 编程 程序员 架构 面试

适用初学者的5种Python数据输入技术

华为云开发者联盟

Python 数据 函数

容器和虚拟机到底有啥区别?

网管

容器 虚拟机

SpringBoot-技术专题-Hystrix学习介绍

洛神灬殇

史上最通俗Netty入门长文:基本介绍、环境搭建、动手实战

JackJiang

网络编程 Netty nio 即时通讯 IM

《程序员面试金典》.pdf

田维常

面试

2020年底备战—从技术到面试合集

iOSer

ios 编程 面试

基于Vue实现一个有点意思的拼拼乐小游戏

徐小夕

Java GitHub 开源 H5游戏 H5

影视剪辑类自媒体运营心得:如何抓住观众的痛点

石头IT视角

区块链币支付系统开发搭建,USDT支付平台源码

13530558032

vscode + vim : vscode 全键盘使用方案

lmymirror

vim vscode Spacemacs

阿里P8大牛整理的300页图解网络知识+计算机底层操作系统

Java架构之路

Java 编程 程序员 架构 面试

我从“过时”的 React 开发中汲取经验教训_架构/框架_InfoQ精选文章