图片

2012年,Angular.js 一经推出便改变了前端开发的格局,迅速取得成功。仅在两年之后,Angular 团队推出了 Angular 2,然而他们根据一组不同的范例重写了整个原始库。许多开发人员,包括我自己,都不想重写现有的应用程序来适应这些新想法。对于新项目来说,Angular.js 不再是首选,因为其他框架也同样出色。

2015 年,我们开始使用 React 来构建前端。React 不仅拥有简单的架构,而且还注重组件,再加上稳定的生产力,无论代码库大小如何都是一个很好的选择。React 大受欢迎,社区发展迅速。最近,React 和 Next.js 团队一直在推广服务器组件,这是一种构建 Web 应用程序的新方法,但不适合大多数现有的 React 应用。

这种变化是否堪比从 Angular.js 到 Angular 2 的转变?React 是否正在重蹈 Angular.js 的覆辙?

注意:在本文中,我将讨论 React 和 Next.js 团队引入的新功能。由于这两个团队密切合作,因此很难说哪个团队负责哪个功能。因此,我将使用“React”来指代这两个团队。

重新学习一切 

React 的核心是一个视图库。这一点没有变化,对于 React 服务器组件,你仍然可以使用 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 对其进行了加强,能够自动去除重复的请求。为什么说这是必要的?如果你需要通过组件树更深入地访问获取的数据,就不能将其放置在 React 上下文中,因为服务器组件中不能使用 useContext。因此,在组件树中的不同点访问同一个获取数据的推荐方法是,根据需要重新获取,并依靠 React 来避免重复请求。

默认情况下,无论响应缓存的标头如何,这个 fetch 函数都会缓存数据。实际的获取过程发生在构建时。

如果你想通过一个按钮来启动一个 POST 动作,就必须在表单中添加 fetch 函数,并使用服务器动作,这意味着使用一个带有 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 钩子:useState、useContext、useEffect 将导致服务器组件出错。如果你需要这些钩子,就必须使用 use client,强制 React 在客户端渲染组件。注意,现在这个功能是默认启用的,而在Next.js中,甚至在服务器组件出现之前,它就是默认行为了。

图片

CSS-in-JS与服务器组件不兼容。如果你习惯了使用 sx 或 css 属性直接设置组件样式,则必须学习 CSS Modules、Tailwind 或 Sass。在我看来,这种变化感觉像是倒退了一步:

// in app/dashboard/layout.tsximport 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 {    padding24px;}

那么,调试呢?我只能祝你好运。React DevTools 不显示 React 服务器组件的详细信息。你无法在浏览器中检查组件,查看它使用的 props 或其子组件。目前,调试 React 服务器组件只能求助于 console.log。

虽然基础的 JSX 保持不变,但服务器组件的基本思想与客户端 JS 完全不同。即便你是一名精通 React 的开发人员,也不一定对掌握服务器组件有很大帮助。基本上,你必须重新学习所有内容,除非你熟悉 PHP。

注意:公平地说,我提出的大多数问题都涉及到带有“alpha”标签的功能。这些问题可能会在稳定版本发布之前得到解决。

生态系统为零

如上所述,获取数据不能再使用 react-query。事实证明,这不是唯一无法结合 React 服务器组件一起使用的库。如果你依赖下面的工具,那么就要开始寻找替代品了:

  •  material-ui
  •  chakra-ui
  •  Emotion
  •  styled-components
  •  React-query
  •  swr
  •  react-hook-form,
  •  适用于大多数SaaS 提供商的 SDK
  •  以及其他等等。

这些库都使用了标准的 React 钩子,从服务器组件调用时会失败。

图片

如果你需要这些库,则必须使用 use client 指令将它们封装在组件中,强制客户端渲染。

强调一下:React 服务器组件破坏了几乎所有现有的 React 第三方库,这些库的作者必须修改代码才能兼容。也许有些作者会修改代码,但有些不会。即使他们愿意修改,也需要一定的时间。

因此,如果你使用 React 服务器组件启动应用,则不能依赖现有的 React 生态系统。

更糟糕的是:客户端 React 提供了很多日常工作需要的工具,但服务器组件中并没有提供。例如,React Context 是管理依赖注入的绝佳解决方案。如果没有 React Context,服务器组件就需要一个依赖注入容器,类似于Angular 的方法。如果核心团队不提供,则不得不依靠社区。

与此同时,你必须手动编写很多代码。在没有 UI Kit、表单框架、智能 API 客户端以及你喜欢的 SaaS 的 React 集成的情况下构建 React 应用会变得非常具有挑战性。

当前的 React 生态系统是其最大的优势。这也是 React 如此广泛使用的原因。而 React 服务器组件打破了这一优势。

黑魔法太多了

服务器端渲染是一个已解决的问题。服务器端脚本接收请求、获取数据并生成 HTML。客户端渲染同样成熟。浏览器检索数据,客户端脚本更新 DOM。

React建议混合服务器端和客户端渲染,感觉就像是在使用黑魔法。你可以在服务器组件中使用客户端组件,反之亦然。

图片

当客户端组件渲染服务器组件时,React 服务器不会发送 HTML,而是发送组件树的文本表示。然后由客户端脚本在客户端渲染组件树。

如果你习惯使用 HTML 或 JSON 调试 AJAX请求,就会大吃一惊。这里由一个 RSC Wire 格式的例子(https://www.plasmic.app/blog/how-react-server-components-work#the-rsc-wire-format),React 使用这种格式将更新从服务器组件流式传输到客户端:

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服务器组件看似非常神秘,对大多数开发人员来说都很难理解或调试。不确定这种变化究竟是会提高生产力,还是会阻碍生产力。

我们真的需要这个组件吗?

从第一原则触发考虑 Web 开发,可以合理地得出这样的结论:使用少量 AJAX 的服务器端渲染是构建 Web 应用程序的有效方法。Dan Abramov 在 2023年 Remix 大会的演讲中对React 服务器组件背后的动机做出了解释(https://www.youtube.com/watch?v=zMf_xeGPn6s&ab_channel=Remix):

图片

这种架构非常适合电子商务网站、博客以及其他对 SEO 有强烈要求的、以内容为中心的网站。

然而,这并不是一个新概念。多年来, 这种架构一直用于 Rails 中的 Hotwire 或 Symfony 应用程序等工具。

此外,服务器组件旨在解决的一些问题(如数据获取、部分渲染等)已被一些单页应用程序框架解决了,比如我们自己的 react-admin。还有一些问题,比如管理面板、SaaS、B2B 应用程序、内部应用程序、CRM、ERP、长期应用程序等,都算不上真正的问题。

这就是为什么许多 React 开发人员,包括我自己,都非常满意单页应用程序架构。当需要做一些服务器端渲染时,我可能会选择一个生态系统比 React 服务器组件更成熟的工具。

那么,如果我并不需要React 服务器组件,那么又何必如此担忧呢?

构建 React 应用的标准方法

我担心的第一个问题是,React 的这种变化是在阻止人们使用单页应用程序架构。或者说,React不鼓励人们在没有框架的情况下使用 React,然而React 框架本身却开始推崇服务器端渲染。

图片

我担心的第二个问题是,官方 React.js 文档建议使用 Next.js。而 Next.js 官方文档建议从 13.4 版开始使用 React 服务器组件。

换句话说,根据官方文档,React 服务器组件是构建 React 应用程序的默认方式。React 生态系统的新手自然会使用它们。

我认为这还为时过早。 Dan Abramov 也认为:

“为了让新范式发挥作用,我们还需要付出大量努力。”

React服务器组件需要新一代路由器、新一代捆绑器。但这些范式仍处于 alpha 阶段,尚未准备好投入生产。

那么,为什么 Next.js 如此激进呢?

我不得不认为,Next.js 的新方向并不是为了帮助开发者,而是为了帮助 Vercel 销售 React。你无法真正围绕 SPA 销售服务:一旦编译,SPA 就是一个可以在任何地方免费托管的单个 JS 文件。但是服务器端渲染的应用需要服务器才能运行。服务器是可以销售的产品。也许我是一个阴谋论者,但我看不出还有其他理由像这样破坏 React 生态系统。

 

现有应用不受影响

与 Angular.js 到 Angular 2 的过渡不同,React 服务器组件的引入并不是重大变化。现有的单页应用程序仍然可以使用最新版本的 React。使用 Pages 路由器构建的现有 Next.js应用程序也可以正常工作。

那么,问题:“React 会重蹈 Angular.js 的覆辙吗?”答案是,不会。

但如今开始一个新项目,你会选择什么?具有成熟工具和生态系统(单页应用)的健壮架构,还是 React 团队正在强推的新框架(服务器组件)?这是一个艰难的选择,人们可能会寻找替代方案,而不是冒险做错误的选择。

我个人认为,React 希望通过一个工具满足所有 Web 开发人员需求的愿景过于激进,或者说当前的解决方案不正确。Next.js 文档中有一个下拉列表,允许读者在App 路由器(服务器组件)和 Pages 路由器之间进行选择。如果一个工具提供两种截然不同的方法来做同一件事,那么它还是同一个工具吗?

图片

所以,问题:“React 是否会因为过于激进而损害整个社区?”我认为答案是肯定的。

总结

也许服务器组件代表服务器端框架的进步,或者至少,等到准备好投入生产,它们真的能够发挥积极的作用。但对于更广泛的 React 社区来说,我害怕这一变化会导致社区分裂,并有可能危及 React 多年来建立的优势。

我希望 React 和 Next.js 团队采用更加折中的方法。我希望 React 团队认识到单页应用架构是一个正确的选择,并且短期内不会消失。我更希望看到 Next.js 在他们的文档中淡化服务器组件,或者至少明确标注“alpha”功能。

也许是我过于急躁,也许这种变化代表了未来的趋势。也许开发人员注定要不断地在范式之间来回转换,这就是这个行业的本质。

Loading

作者 amtbbsportal