在 Beta 版本的 React 新文档中,是有在显眼的位置提到推荐使用 Vite 启动的,而正式版文档则替换成了 React 社区的几个知名框架:
这件事还引起了尤雨溪和 Dan 的一些讨论:
这是一些有意思的花絮,当然本文主要要讨论的问题是,我们曾经创建一个 React 项目时必备的 Create React App 脚手架去哪里了?为什么官网已经不在提及,是被时代抛弃了嘛?
接下来是 Dan 关于这个问题的回复的译文:
大家好。
我们知道这一直是个痛点,我们正在制定一个解决方案。由于该拉取请求旨在开始讨论,现在是提供有关我们对 Create React App 未来思考的背景的好时机。我们希望明确我们的推理和我们正在考虑的权衡,因此这将是一个长评论,包含几个部分。如果你感到不耐烦,请滚动到最后一节,了解我们的建议。
# Create React App 的存在原因
为了提供这次讨论的历史背景,我想重新讲述一下 Create React App 的故事并追溯其发展历程。
当我们在 2016 年发布 Create React App 时,工具生态系统非常分散且没有集成。如果你要将 React 添加到现有应用程序中,你需要添加一个<script>
标签或从 npm 中导入,然后调整现有的构建工具配置。但是,如果你要从头开始构建一个完全使用 React 构建的新应用程序,就没有明确的方法可以做到这一点。
在 Create React App 之前,你必须安装一堆工具并将它们连接在一起,提供正确的预设来使用 JSX,在开发和生产环境中进行不同的配置,提供正确的设置以进行资产缓存,配置 linter 等等。这样做是非常棘手的。人们通过创建和共享“boilerplate”存储库来应对这个问题,你可以克隆它,然后调整它以适应你的项目,但更新它会很困难。你的项目设置将变得陈旧,你要么放弃更新,要么花费大量精力让所有工具再次协同工作。在一个快速发展的生态系统中,这非常困难。
Create React App 通过将几个工具组合在一个软件包中,选择一个合理的默认配置,然后遮盖所有工具之间的小不兼容性来解决了这个问题。现在,如果你想用 React 开始一个新项目,就有了一个明确的推荐方法!然后,偶尔更新这个软件包,就可以免费获得所有底层工具的更新。这种模型变得非常流行,今天有整个类别的工具以这种方式工作。Vite 确实是其中一个最好的工具,它具有相似的愿景,并在某些方面将其推向了更远。
Create React App 的目标是为大多数 React 用户提供开始新的 React Web 应用程序的最佳方法。它支持一组经过筛选的功能,这些功能可以协同工作。随着时间的推移,“基线”提供的开箱即用功能将扩展,因为我们找到了正确的权衡。例如,我们添加了运行时错误的覆盖层。我们添加了对不同样式选项的支持。我们默认添加了 Fast Refresh,允许你保存组件的代码并在不丢失状态的情况下查看更改。这是默认的 React 开发体验的重要里程碑。总的来说,由于 Create React App 完全控制了编译管道,为所有人添加与编译相关的功能非常容易。
像这样的筛选设置对生态系统仍然很有价值。当 React Hooks 推出时,我们在默认设置中添加了 React Hooks lint 规则。除了成为开始项目的简单方法外,Create React App 还允许 React 团队向最广泛的受众部署重要的工具更改(Fast Refresh 支持,React Hooks lint 规则)。如果没有由 React 团队策划的流行模板,要向如此广泛的用户推出这些工具更改将是困难的。
# Create React App 的问题
随着时间的推移,Create React App 已经停滞不前。许多人在此线程中指出它比其他替代方案慢,而且不支持一些人们今天想要使用的流行工具。原则上,我们可以解决这些问题。例如,我们可以更新 Create React App 的内部以使用更快的打包工具,甚至使用 Vite 内部。或者我们可以建议人们从 Create React App 迁移到类似 Vite 的东西。但是,我们想要解决一个更深层次的问题。
按设计,Create React App 生成一个纯客户端应用程序。这意味着使用它创建的每个应用程序都包含一个空的 HTML 文件和一个包含 React 和应用程序包的<script>
标签。当空的 HTML 文件加载时,浏览器等待 React 代码和整个应用程序包下载。在低带宽连接上,这可能需要一段时间,而且在此过程中用户根本看不到屏幕上的任何内容。然后,你的应用程序代码会加载。现在,浏览器需要运行所有代码,这可能在性能较差的设备上很慢。最后,在此时点,用户才会看到屏幕上的内容,但通常你还需要加载一些数据。因此,你的代码会发送一个请求来加载一些数据,而用户正在等待它回来。最后,数据加载完成,组件重新呈现数据,用户看到最终结果。
尽管这很低效,但如果仅在客户端上运行 React,很难做得更好。将其与像 Rails 这样的服务器框架进行比较:服务器会立即开始获取数据,然后生成包含所有数据的页面。或者使用静态构建时间框架(如 Jekyll)进行比较,它在构建期间执行相同的操作,并生成一个 HTML+JS+CSS 包,可以部署到静态主机上。在这两种情况下,用户将看到包含所有信息的 HTML 文件,而不是等待脚本加载的空文件。HTML 是 Web 的基石——为什么创建“React 应用程序”会生成一个空的 HTML 文件?为什么我们没有利用 Web 的最基本功能——在所有交互式代码加载之前快速查看内容的能力?为什么我们要等到所有客户端代码完成加载后才开始加载数据?
Create React App 只解决了问题的一方面。它提供了良好的(当时!)开发体验,但是它没有强制实施足够的结构来帮助你利用 Web 的强项来提供良好的用户体验。你可以尝试自己解决这些问题,但那将涉及“ejecting”并显着自定义你的设置,这违背了 Create React App 的初衷。每个真正高效的 React 设置都是自定义的,不同的,并且使用 Create React App 无法实现。
这些用户体验问题不是特定于 Create React App 的。它们甚至不是特定于 React。例如,从 Vite 主页模板为 Preact,Vue,Lit 和 Svelte 创建的应用程序都遭受了所有相同的问题。这些问题与没有静态站点生成(SSG)或服务器端渲染(SSR)的纯客户端应用程序有关。
尽管 Facebook.com 从 Hack/XHP 渲染到 React 的重写没有 SSR 不可能发布(这最终对性能至关重要),但上述问题并不是只影响像 Facebook 这样的大型应用程序。恰恰相反!如果你考虑完全在 React 中开发的许多应用程序,你会发现各种内容导向的应用程序将从 SSG 或 SSR 中受益。作品集,博客,报纸,电子商店——它们根本不可能提供空的 HTML 文件。这就是为什么我们总是建议使用具有 SSG 功能的 React 框架来进行内容导向站点的原因。
# React 框架的崛起
有些人可能不愿意完全使用 React 来构建,这是一个有效的选项。例如,你可以使用不同的工具(如 Jekyll 或 Astro)在服务器上或构建期间生成 HTML 页面。这解决了空 HTML 文件的问题,但你必须混合两种渲染技术(例如,Jekyll 模板用于页面的“外部”部分,React 组件用于页面的“内部”部分)。你想要添加的交互性越多,这种技术上的分裂就越明显。
这种分裂不仅会影响开发者的体验,用户的体验也会受到影响。对于那些真正以 HTML 为中心且没有充分利用 React 的工具,每次页面导航都会变成完整的页面重新加载,清除所有客户端状态。今天,许多用户期望平滑的应用内导航(假设以正确和可访问的方式完成)而不是 90 年代风格的全页面重新加载。同样,许多开发人员更喜欢使用单一的渲染模型来构建应用程序,而不是混合两个不同的模型。人们希望使用 React 来构建整个应用程序。我们也希望能够实现这一点。
如果你使用 React 来构建整个应用程序,能够使用 SSG/SSR 是很重要的。Create React App 中缺乏对它们的支持是很明显的。但这不是 Create React App 落后的唯一领域。在生态系统中经过多年的创新之后,许多其他问题现在都有了成熟的解决方案,适用于 React。例如,让我们看看网络瀑布和捆绑大小。
即使你的应用程序不像面向内容的站点那样受益于 SSG 或 SSR,它可能仍然受到网络瀑布的影响。如果你在“挂载”时获取数据,你的第一个数据获取甚至不会开始,直到所有代码都加载完成并且组件已经渲染。这是一个瀑布 —— 如果你的应用程序“知道”如何在代码仍在加载时开始获取数据,它可以并行进行。在导航时,如果父组件和子组件都需要获取某些东西,这会导致更严重的瀑布效应。当我们谈论 React 性能时,无法逃脱的事实是,对于如此多的应用程序,瀑布是性能瓶颈。要解决这些瀑布,你需要将数据获取与路由集成,而 Create React App 并不这样做。
与 React 不同,它有一个固定的捆绑大小,而你的应用程序代码随着每个新功能和额外的依赖项的添加而增长。如果你经常部署,你的应用程序在每次使用时加载会变得非常缓慢,因为它总是需要加载所有代码。你可以通过几种方式来解决这个问题。你可以拒绝某些功能,但这并不总是有效的。你可以将一些代码移动到服务器上或在构建期间运行(如果你的工具允许这样做)。理想情况下,你还应该按路由分割代码:如果仪表板页面需要呈现图表,则没有必要在账单页面上加载整个图表的实现。然而,如果你尝试手动进行代码拆分,你经常会使性能变差。例如,如果你懒加载图表,但图表在“挂载”时加载其数据,你刚刚引入了另一个网络瀑布。要解决这个问题,需要将数据获取与路由和捆绑集成在一起,而 Create React App 并不这样做。
React 本身只是一个库。它不指导如何进行路由或数据获取。Create React App 也是如此。不幸的是,这意味着 React 单独或 Create React App 作为最初设计的工具都无法解决这些问题。正如你所看到的,这不仅仅是一个缺失的功能。这些功能 —— 服务器端渲染和静态生成、数据获取、捆绑和路由 —— 是相互关联的。当 Create React App 推出时,React 还非常新,每个功能应该如何在隔离的情况下工作仍有许多问题需要解决,更不用说如何将它们最好地组合在一起了。
时代在变化。现在越来越难以推荐一个没有这些功能的解决方案。即使你不立即使用所有这些功能,当你需要它们时,它们应该对你可用。你不应该不得不迁移到不同的模板并重构所有代码才能利用它们。同样,不是所有的数据获取或代码拆分都需要基于路由。但这是一个应该适用于大多数 React 应用程序的好默认值。
虽然你可以自己集成所有这些组件,但很难做到很好。就像 Create React App 自身集成了与编译相关的几个问题一样,像 Next.js、Gatsby 和 Remix 这样的工具更进一步 —— 将编译与渲染、路由和数据获取集成在一起。这类将编译、渲染、路由和数据获取集成在一起的工具被称为“框架”(或者,如果你更喜欢将 React 本身称为框架,你可能会称它们为“元框架”)。这些框架对于应用程序的结构提出了一些意见,以提供更好的用户体验。许多开发人员也发现,推荐的可扩展内置路由和数据获取解决方案是符合人体工程学的。
将 React 视为一种架构
我们喜欢 React 的灵活性。你可以使用 React 来构建一个单独的按钮,也可以使用它来构建一个完整的应用程序。你可以使用它来在一个二十年历史的 Perl 网站内构建一个内部仪表盘,或者你可以使用 React 来构建一个混合 SSG/SSR 电子商务网站。这种灵活性是必不可少的,我们知道我们的用户也喜欢它。它不会消失。
然而,我们还希望为完全使用 React 构建的新应用程序鼓励最佳默认设置。如果默认建议的创建 React 应用程序的方式支持 SSG 和 SSR、自动代码拆分、无客户端-服务器瀑布流、路由预取、保留客户端 UI 状态的导航以及其他功能,以实现出色的用户体验,那将是很好的。至少,建议的默认创建 React 应用程序的方式不应该因为固有的仅客户端架构而完全无法集成这些功能,而无法实现这些功能。
这是一个机会。对于框架来说,挑战在于将这些关注点与出色的性能和人机工程学集成起来。然而,对于 React 来说,也存在一个挑战。我们意识到,帮助 React 框架提供出色的用户体验的最佳方法是专注于 React 本身的基础原语。在渲染层面,React 本身可以做出一些独特的事情,这些事情可以为框架在其他所有层面上所能做的事情提供强大支持。例如,就像 <Suspense>
一样,单个 React API 可以在幕后解锁 一整个框架优化的范围。
这就是为什么我们认为将 React 视为两个东西是有帮助的。
React 是一个库。这个库提供了一些 API,让你定义和组合组件。React 也是一种架构。它提供了建立框架的基础模块,使框架作者能够充分利用其渲染模型。你可以在没有框架的情况下使用 React。但我们希望确保,如果你与框架一起使用它,框架能够充分利用 React 本身。我们过去几年开发的许多功能 (<Suspense>
、useTransition
、像renderToPipeableStream
这样的流式 API,以及实验性的 Server Components) 都是面向框架的。它们通过集成捆绑、路由和数据获取,让框架充分利用 React。
你已经可以看到一些框架在采用这些功能,如 Next 13、Gatsby 5 和 Remix 1.11。还有很多工作要做,其中一些工作正在逐步推出实验阶段(因此文档仍然稀缺)。但我们很高兴看到我们多年的努力得到回报,通过默认情况下启用的功能,使 React 框架(及其用户)能够更快地发布应用程序。
这就引出了下一个问题。
一个库,多个框架
不止一个 React 框架。这是好事。
尽管存在关于变动的担忧,但 React 生态系统因有许多参与者而变得更好。有多个竞争的数据获取解决方案和路由解决方案。选项每年都在变得更好。毫不奇怪,也会有多个解决方案来集成路由、数据获取、渲染和编译,即多个 React 框架。
我们希望保持这种状态。但是,我们也希望在可能的情况下鼓励收敛,并使其有利于 React 生态系统。例如,不同的框架可能使用不同的机制来加载数据。但是,如果它们都采用 <Suspense>
作为加载指示器,我们基于 <Suspense>
的更高级功能将适用于所有框架。我们仍在努力最终确定和记录框架的 API,但我们希望赋予它们充分利用 React 的能力。
有些项目永远不会适合任何流行框架的模式,这是可以的。也许您正在开发一个需要与 PHP 站点集成的内部仪表板,而没有任何框架可以让您轻松地做到这一点。这是 Parcel 或纯客户端 Vite 模板等低级工具的绝佳用例。也许你的应用程序是一个绘图编辑器,你没有路由,甚至不想从 SSG 中退出。我们一直支持,也将支持这一点。将 React 作为库而不是作为框架架构使用是有效的。我们只认为对于大多数新的 Web 应用程序来说,这不是正确的默认选择。
如果大多数 React 应用程序的最佳方式是从框架开始,那么我们应该建议哪个框架?我们应该挑选一个吗?我们如何决定选择哪一个?如果它随着时间的推移变得停滞不前怎么办?还存在一个更敏感的激励问题。流行和维护良好的框架通常与与其相关的某种商业提供有关-无论是直接还是间接的。这些提供可能资助这些框架的开发,但我们希望避免向仅与特定托管平台配合使用的产品推荐人们。这就带我们来到了本主题的问题。
我们应该怎么处理 Create React App?
Create React App 的原始目标是:
- 提供一种无需配置即可开始新的 React 项目的简单方法。
- 集成与编译相关的依赖项,使其易于升级。
- 让 React 团队尽可能广泛地部署工具更新(例如 Fast Refresh 支持、Hooks lint 规则)。
然而,它不再满足成为创建 React 应用程序的最佳方式的原始目标。通过提高门槛并将编译与渲染、路由和数据获取集成,框架可以让其用户创建 React 应用程序,这些应用程序:
- 充分利用 Web API,无论应用程序大小是小还是大,都可以提供快速的应用程序和网站。
- 充分利用 React 本身及其框架级特性。
- 提供路由和数据获取,让开发人员陷入“成功之坑”。
React 生态系统应该有一个默认的建议方法,可以充分利用 Web 和 React 本身。这甚至不意味着一定要依赖于 Node.js 服务器。许多流行的框架不需要服务器,可以在 SSG 模式下工作,因此它们也可以解决“完全静态”使用情况。框架的优点在于,如果您以后需要 SSR,您不需要迁移。它是可用的,其他东西也是可用的(例如 Remix 提供了一个开箱即用的变异 API)。
我们如何实现这个愿景?我们看到了一些选择。
选项 1:从头开始创建一个新的框架
我们可以尝试将 Create React App 重新架构为一个集成数据获取、路由、打包和 SSG/SSR 的框架。在这些问题的交集处构建一个高质量的新框架是一个巨大的任务,需要大量的专业生态系统专业知识,即使我们停止其他项目来完成这个任务,它也存在成为像 Create React App 本身一样难以维护的显著风险。它还将进一步碎片化生态系统,带来另一个官方推荐的框架,尽管没有真正的用户。我们认为这个选项在这个时候不切实际。
选项 2:弃用 Create React App,维护一个 Vite 模板
我们可以弃用 Create React App,而是维护我们自己的 Vite 模板。为了实现所述目标,这个模板必须非常复杂。事实上,它必须像一个 React 框架一样复杂,并对路由、数据获取等方面提出意见。这导致了同样的问题:我们实际上正在创建另一个框架。
选项 3:弃用 Create React App,建议使用 React 框架
我们可以淡化或弃用 Create React App 作为一个工具,并更积极地强调 React 框架。这并不意味着您必须使用 React 框架,但我们会建议大多数应用程序使用其中之一。缺点是我们将不再拥有一个中立品牌的 CLI“网关”来创建 React 应用程序:您必须在相应框架的文档中找到正确的框架。直接弃用也会带来一些混乱。我们需要保持命令的可用性很长一段时间——从品牌的角度来看这很令人困惑(为什么创建 React 应用程序被弃用了?)
选项 4:使 Create React App 使用单个框架
我们可以选择一个单一的指定框架,并将 Create React App 更改为默认创建该框架的应用程序。这种方法的主要问题是,它使其他解决方案很难竞争——尤其是如果它们具有略微不同的权衡,但在受欢迎程度、特性集和质量上大致相同。这种行为上的变化也必须相当具有破坏性,因为所有旧的教程都将以一种不明显的方式中断。
选项 5:将 Create React App 变成启动器
我们可以保留 Create React App 作为一个命令,但将其变成一个启动器。它将建议一系列推荐的框架,然后是“经典”的无框架方法作为最后的选择。最后一个“经典”的方法将像 CRA 现在一样产生一个仅客户端的应用程序(为了避免破坏教程),但最终可能会在幕后使用 Vite。
要进入策划的框架列表,React 框架必须满足某些标准——类似于 此文档页面 中实际发生的情况。我们需要考虑社区中的受欢迎程度和采用率(以保持列表的简短),特性集、性能特征、能够充分利用 Web 平台和 React 本身、是否正在积极维护以及是否清楚如何在各种托管服务和环境上托管它(以避免任何供应商锁定)。每个框架的起始模板将由 React 团队维护,以确保它们具有一致的设计和品牌,不链接到商业服务,并且结构相似。我们需要清楚地向社区传达我们做出选择的方式,并定期重新评估它们。
我们的建议
我们目前倾向于选项 5(将 Create React App 变成启动器)。 Create React App 的原始目标是为大多数 React 用户提供开始新的 React Web 应用程序的最佳方式。我们喜欢将其重新用作启动器,明确地传达了关于 对于大多数新 Web 应用程序,我们认为最好的方式已经发生了变化 的转变,同时为旧的工作流程留出了一个逃生口。与选项 3 不同,它避免了“创建 React 应用程序”被弃用的感觉。它承认了现实,需要一点选择,但现在有了 真正 出色的选择。
我们将致力于制定一个更详细的 RFC 提案,详细说明这些观点。与此同时,我们很乐意听取您对这些观点的反馈。我知道这是一个长评论,但我想展示整个思考过程,并利用这个机会澄清 React 和框架之间的关系。我会尽力回答这里的后续问题。
谢谢阅读!