React 困境与未来,何时迎来自己的“Angular.js 时刻”?
2012 年,横空出世的 Angular.js 一举改变了前端开发的格局,在市场上迅速取得成功。仅仅两年之后,开发团队又推出了 Angular 2,基于新的范式对原始库进行全面重写。但包括我自己在内,不少开发者都不愿修改现有应用来适应新的设计思路。于是在新项目中,Angular.js 不再作为优先选项,市面上其他出色的框架开始迎来自己的机会空间。
2015 年,我们开始在前端开发中使用 React。更简单的架构、对组件的高度关注,以及在大小代码库上始终如一的稳定生产力,让 React 很快成为备受好评的新选择。旺盛的人气之下,React 社区也开始茁壮成长。可最近,React 和 Next.js 团队开始推广其服务端组件——这种新的 Web 应用程序构建方式虽有不少优势,但并不适合大部分现有 React 应用。
难道说 Angular.js 到 Angular 2 的故事又要重演?React 是否正在重蹈前辈的覆辙?
注意:本文主要讨论 React 和 Next.js 团队引入的新功能。由于双方的密切合作,这里难以区分各项功能具体出自哪支团队之手。因此,下文将以“React”统一指代双方团队。
React 的核心是一套视图库,这一点仍然保持不变:使用 React Server 组件,大家仍可以使用 JSX 构建组件,并以 props 的形式渲染并传递动态内容:
function Playlist({ name, tracks }) {
return (
<div>
<h1>{name}</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Artist</th>
<th>Album</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
{tracks.map((track, index) => (
<tr key={index}>
<td>{track.title}</td>
<td>{track.artist}</td>
<td>{track.album}</td>
<td>{track.duration}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
但除此之外,其他的一切都随着服务端组件的出现而有所变化。数据获取不再依靠 useEffect 或者 react-query 实现;相反,我们需要在异步组件中使用 fetch:
async function PlaylistFromId({ id }) {
const response = await fetch(`/api/playlists/${id}`);
if (!response.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error('Failed to fetch data');
}
const { name, tracks } = response.json();
return <Playlist name={name} tracks={tracks} />;
}
注意,这里的 fetch 函数跟浏览器 fetch 不同。React 对其进行了增强,能够自动请求重复数据删除。为什么一定要这样调整?如果我们需要在组件树中深入访问获取的数据,由于于 useContext 已在服务端组件中被禁用 ,所以无法将 fetch 放置在 React Context 当中。现在若需要在组件树内的不同点处访问获取的数据,推荐方法是在必要时执行重新获取,再通过 React 执行重复数据删除。
这个 fetch 函数还会默认缓存数据,无论响应缓存标头如何。实际获取过程发生在构建过程中。
如果大家希望创建一个按钮来启动 POST 操作,现在需要将其包含在表单内并使用服务端操作,也就是使用带有 use server 的函数:
export function AddToFavoritesButton({ id }) {
async function addToFavorites(data) {
'use server';
await fetch(`/api/tracks/${id}/favorites`, { method: 'POST' });
}
return (
<form action={addToFavorites}>
<button type="submit">Add to Favorites</button>
</form>
);
}
典型的 React hooks(包括 useState, useContext, useEffect)现在都会导致服务端组件出错。如果仍须使用,大家只能借助 use client escape 路由,也就是强制 React 在客户端渲染组件。请注意,这本是 Next.js 中的默认操作,但在引入服务端组件之后成了可选功能。
CSS-in-JS 跟服务端组件也不兼容。如果大家习惯了使用 sx 或者 css prop 直接设置组件样式,现在就必须学习 CSS Modules、Tailwind 或者 Sass。对我来说,这种调整更像是倒退:
// in app/dashboard/layout.tsx
import styles from './styles.module.css';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode,
}) {
return <section className={styles.dashboard}>{children}</section>;
}
/* in app/dashboard/styles.module.css */
.dashboard {
padding: 24px;
}
那调试受了什么影响?恭喜了家人们,React DevTools 无法显示 React 服务端组件的详细信息。我们无法在浏览器中检查组件以查看它使用的具体 props 或子组件。目前,调试 React 服务端组件的唯一方式就是借助 console.log。
服务端组件的认知模型与客户端 JS 完全不同,只有底层 JSX 保持不变。所以哪怕大家精通 React 开发,在面对服务端组件时还是得重新学习——除非您已经拥有丰富的 PHP 开发经验。
说实话,React 中的新功能大部分处于“Alpha”早期阶段,也许未来会在稳定版发布时得到解决。
如前所述,现在我们没法用 react-query 进行数据获取。事实证明,它绝不是唯一跟 React 服务端组件不兼容的库。如果各位用惯了以下工具,是时候寻找替代方案了:
material-ui,
chakra-ui,
Emotion,
styled-components
React-query,
swr,
react-hook-form,
大部分 SaaS 提供商的 SDK
还有更多。
这些库通通使用标准 React hooks,所以通过服务端组件调用时会出错。
如果大家需要这些库,就只能使用 use client 指令将它们封装在强制客户端渲染的组件当中。
强调一下:React 服务端组件几乎破坏了一切现有 React 第三方库,这些库的作者必须修改代码以重新兼容。有些人会出手,有些人可能放着不管。哪怕是前一种情况,这个过程也需要时间。
所以如果大家使用 React 服务端组件启动应用,那现有 React 生态系统将瞬间不复存在。更要命的是:客户端 React 还提供服务端组件尚未涵盖的多种日常工具。例如,React Context 就是管理依赖项注入的绝佳方案。如果没有 React Context,那服务端组件就需要单独的依赖项注入容器(Dependency Injection Container,类似 Angular 的办法)。如果核心团队不帮忙,这活就得靠技术社区完成。
与此同时,我们还需要手动编写大量代码。想象一下,在没有 UI Kit、表单框架、智能 API 客户端和 SaaS 集成的前提下搞开发,其难度可想而知。
原先的 React 生态系统已经成了该项目最显著的优势,也是 React 得到广泛的普及的根本原因。可如今,React 服务端组件可谓是自毁长城。
服务端端渲染早就有其成熟方案。服务端端脚本接收请求,获取数据并生成 HTML。客户端渲染也是一样,浏览器检索数据、客户端脚本随后更新 DOM。
但 React 偏要力推服务端端加客户端混合渲染,属于没有困难硬是创造困难。这样大家既可以在服务端组件中使用客户端组件,又可以在客户端组件中使用服务端组件。
当客户端组件渲染服务端组件时,React 服务端不会发送 HTML,而是发送组件树的文本表示。之后,客户端脚本会在客户端上渲染该组件树。
如果大家习惯了使用 HTML 或者 JSON 来调试 AJAX 请求,肯定会对此大吃一惊。下面来看 React 如何用 RSC Wire 格式将更新从服务端组件流式传输至客户端:
M1:{"id":"./src/ClientComponent.client.js","chunks":["client1"],"name":""}
S2:"react.suspense"
J0:["$","@1",null,{"children":[["$","span",null,{"children":"Hello from server land"}],["$","$2",null,{"fallback":"Loading tweets...","children":"@3"}]]}]
M4:{"id":"./src/Tweet.client.js","chunks":["client8"],"name":""}
J3:["$","ul",null,{"children":[["$","li",null,{"children":["$","@4",null,{"tweet":{...}}}]}],["$","li",null,{"children":["$","@4",null,{"tweet":{...}}}]}]]}]
这种格式没有任何可读性,纯属具体实现。
但 HTTP、JSON 和 JSX 之所以如此流行,靠的就是良好的可读性。而 React 服务端组件显然破坏了这种优势。
React 服务端组件实在晦涩难懂,对大多数开发者而言都难以阅读或调试。这样设计真能提高生产力吗?还是说只会起反作用?
如果单从第一性原理角度出发,那这样修改确有其合理性:使用少量 AJAX 的服务端渲染,能够提高 Web 应用程序的构建效率。Dan Abramov 在 Remix Conf 2023 大会上对 React 服务端组件的开发动机做出了精彩的解释:
这种架构特别适合电子商务网站、博客及其他关注 SEO 的内容为中心类网站。
但这并不是什么新鲜概念。多年以来,Hotwire in Rails 和 Symfony 等应用工具一直在使用这种架构。
此外,服务端组件希望解决的不少问题(包括数据获取、分部渲染等)早已在某些单页应用中有了答案。至于管理面、SaaS、B2B 应用、内部应用、CRM、ERP、长周期运行应用等当中的其他问题(大捆绑包、首次加载缓慢、SEO 等)其实根本不算真正的问题。
正因为如此,大部分 React 开发者才对单页应用架构非常满意。如果真需要做服务端渲染,我们完全可以选择生态系统比 React 服务端组件更成熟的其他工具。
那既然用不上,我们为什么还要认真讨论?
我想强调的第一点,就是 React 正阻止人们使用单页应用架构。或者更确切地讲,他们不鼓励开发者在不配合框架的前提下使用 React,而他们所推荐的框架会更多强调服务端渲染。
还有第二个问题。React.js 官方文档主要推荐使用 Next.js。
Next.js 官方文档则主要推荐使用 React 服务端组件的 13.4 及更高版本。
换句话说,React 服务端组件已经成为构建 React 应用的默认方式。React 生态系统的新手会习惯于直接使用,但在我看来这一切还没准备好。Dan Abramov 也承认这一点:
React 服务端组件要求 router 和 bundler 全面更新换代,但目前这些都处于 alpha 阶段,还远无法适应生产开发的要求。
所以,Next.js 在那急什么劲呢?
我不禁怀疑,Next.js 目前的作法并不是要帮助开发者,而是想帮 Vercel 宣传 React。SPA 本身没有营销空间——一旦编译完成,SPA 就是一个能在任何地方随意托管的 JS 文件。但服务端渲染的应用必须借助服务器才能运行,而服务器显然是可以营销的产品。也许我有点阴谋论的倾向,但除此之外我真的无法理解为什么要如此明目张胆地破坏 React 生态系统。‘
与 Angular.js 到 Angular 2 的过渡不同,React 服务端组件的推出并不算是重大变化。现有单页应用仍可适配最新版本的 React,使用 Pages router 构建的现有 Next.js 应用同样可以正常运行。
因此,对于 React 是否将迎来自己的“Angular.js 时刻”这个问题,答案显然是否定的。但如果大家现在起打算新开一个项目,那会如何选择?是拥有成熟工具和生态系统的健壮架构(单页应用),还是 React 团队强烈推荐的新方案(服务端组件)?这是个艰难的选择,如果人们害怕自己选错,很可能会直接转投其他框架的怀抱。
我个人认为 React 靠单一工具满足所有 Web 开发需求的愿景太过激进——或者说,至少目前的解决思路是有问题的。在我看来,最典型的证明就是 Next.js 文档中的下拉列表——读者可以在 App router(服务端组件)和 Pages router 之间随意选择。如果一款工具为同一种功能提供两种截然不同的实现方法,但它真的还是同一款工具吗?
所以对于“React 过度膨胀的野心是否在损害社区”,我的答案是肯定的。
服务端组件也许的确代表着服务端框架的进步——或者至少在达到生产就绪状态后,应该有其进步意义。但对于更广泛的 React 社区,我担心这股潮流会带来碎片化风险,甚至威胁 React 多年来辛苦建立的江湖地位。
如果开发团队能听到我的声音,那我真诚希望 React 和 Next.js 两家能采取更平衡的方法。希望 React 团队能意识到,单页应用架构是一种非常有效的选项,仍然拥有旺盛的生命力。我也希望看到 Next.js 能在自己的官方文档中淡化对服务端组件的强调,或者至少要明确标注其尚属于“Alpha”功能。
当然,也许事实证明我是错的,服务端组件才是未来。毕竟开发者命里注定要不断适应新的范式,持续变化才是软件行业的永恒本质……谁知道呢?
原文链接:
https://marmelab.com/blog/2023/06/05/react-angularjs-moment.html
相关阅读:
从新 React 文档看未来 Web 的开发趋势 (https://www.infoq.cn/article/Tv3SyqoivXMWUoj8qSMT)
我被 React 劫持了,很痛苦又离不开 (https://www.infoq.cn/article/CZKMjHaxbf1Z7xcSzisX)
如何快速上手 angular.js(https://xie.infoq.cn/article/ec0a13741cd7c4a72eea370ae )
声明:本文为 InfoQ 翻译,未经许可禁止转载。
点击底部阅读原文访问 InfoQ 官网,获取更多精彩内容!
国内权威专家组团向大模型投毒,ChatGPT居然比国内一众中文模型更安全?
OpenAI 遭遇离职潮:员工对 ChatGPT 进展缓慢失望,痛批 CEO 不务正业
阿里改革,P8 成为历史;GPT-4 模型架构泄露;OpenAI 面临最严调查,Altman 惊慌连发 3 推|Q 资讯
微信扫码关注该文公众号作者