大家好,我是 CUGGZ。
最近,网友 t3dotgg 建议把 React 官方文档中关于建议使用 Create React App 来创建新项目更换为建议使用 Vite 来创建新项目。该建议引起了网友的热议,多数网友对此表示赞同:
新的 React 官方文档发布在即(目前显示已完成 99%),Beta 版文档中仍然推荐使用 Create React App 创建新项目。另外提供了两个备选方案:Vite、Parcel。
查看 Create React App 的 Github 仓库可以发现,其已经 5 个月没有更新了,积累了 1500+ 个 issues。
1 月 31 日,React 团队核心成员 Dan Abramov 对此建议进行了回复,解释了 React 团队成员对此建议的权衡并提供了一些选项,下面就来看看详细内容吧(可以跳到最后看结论)!
Create React App 的演变
在 2016 年发布 Create React App 时,工具的环境是分散的。如果想要将 React 添加到现有应用,需要添加一个 <script> 标签或从 npm 中导入,然后调整现有的构建工具配置。但是,如果要从头开始创建一个仅使用 React 构建的新应用,则没有明确的方法可以做到这一点。
在 Create React App 之前,必须安装一堆工具并将它们连接在一起,提供正确的预设以使用 JSX,为开发和生产环境进行不同的配置,为资源缓存提供正确的设置,配置 linter 等,想要正确完成这一系列工作非常困难。人们通过创建和共享可以克隆的“样板”存储库来解决了这个问题。然而,这产生了另外一个问题:一旦在项目中调整了克隆的样板文件,就很难再拉取样板的更新。这样,项目的设置会变得旧,要么放弃更新,要么花费大量精力让所有工具再次协同工作。在快速发展的生态系统中,这非常困难。
Create React App 通过将多个工具组合在一个包中解决了这个问题。现在,如果想用 React 开始一个新项目,有一个明确的推荐方法(Create React App)可以做到这一点!然后,每隔一段时间,可以更新这个包,以获得所有底层工具的更新。这种模型变得很流行,以至于今天有很多工具都以这种方式工作。Vite 确实是拥有相似愿景的最佳工具之一,并且在在某些方面更进一步。
Create React App 的目标是为大多数 React 用户提供启动新 React Web 应用的最佳方式,它支持一组协同工作的精选功能。随着时间的推移,它提供的开箱即用的“baseline”会随着我们找到正确的权衡而扩大。例如,为运行时错误添加了一个遮罩层,添加了对不同样式选项的支持,默认添加了快速刷新,它允许保存组件的代码并查看更改而不会丢失状态。对于默认的 React 开发体验来说,这是一个巨大的里程碑。总的来说,由于 Create React App 完全控制了编译管道,因此添加编译相关的功能是很容易的。
有这样一个精心策划的设置对生态系统仍然很有价值。当 React Hooks 出现时,React 团队将 React Hooks lint 规则添加到默认设置中。除此之外,Create React App 还允许 React 团队向尽可能广泛的受众部署重要的工具更改(快速刷新支持、React Hooks lint 规则)。如果没有 React 团队策划的流行模板,将很难如此广泛地推出这些工具更改。
Create React App 的问题
随着时间的推移,Create React App 停滞不前。许多人指出它比替代品慢,并且不支持人们如今想要使用的一些流行工具。原则上,React 团队可以解决这些问题。例如,可以更新Create React App 内部,以使用更快的 bundler,甚至在内部使用 Vite。或者可以建议人们从 Create React App 迁移到 Vite 这样的应用。然而,React 团队还想解决一个更深层次的问题。
按照设计,Create React App 会生成一个纯客户端应用。这意味着用它创建的每个应用都包含一个空的 HTML 文件、一个带有 React 的 <script> 标签和应用包。当加载空的 HTML 文件时,浏览器会等待 React 代码和全部应用包下载。这在低带宽连接上可能需要一段时间,并且此时用户在屏幕上看不到任何内容。然后,加载应用代码。此时用户会在屏幕上看到一些内容——但通常还需要加载数据。所以代码发送了加载数据的请求,用户需要等待它返回。最后,数据加载,组件重新渲染数据,用户看到最终结果。
这是非常低效的,尽管如果只在客户端运行 React 很难做得更好。将其与 Rails 这样的服务端框架进行对比:服务端将立即启动数据获取,然后生成包含所有数据的页面。在这种情况下,用户会看到包含所有信息的 HTML 文件,而不是等待加载脚本的空白文件。HTML 是Web 的基石——那么为什么创建React 应用会产生一个空的 HTML 文件?为什么不利用 Web 最基本的功能——在所有交互代码加载之前快速查看内容的能力?为什么要等到所有客户端代码加载完成后才开始加载数据?
Create React App 只解决了问题的一方面,它提供了良好的开发体验,但它没有强加足够的结构来帮助我们利用 Web 的强大功能获得良好的用户体验。开发者可以尝试自己解决这些问题,但这违背了 Create React App 的宗旨。每个真正高效的 React 设置都是自定义的、不同的,并且是 Create React App 无法实现的。
这些用户体验问题并不是 Create React App 特有的。它们甚至不特定于 React。例如,从 Preact、Vue、Lit 和 Svelte 的 Vite 主页模板创建的应用都会遇到相同的问题。这些问题是没有静态站点生成 (SSG) 或服务端渲染 (SSR) 的纯客户端应用所固有的。
React 框架的兴起
有些人可能不喜欢完全使用 React 进行构建。例如,可以在服务端或在构建过程中使用不同的工具(如 Jekyll 或 Astro)生成 HTML 页面。这解决了空 HTML 文件的问题,但是必须混合使用两种渲染现技术。随着时间的推移,想要添加的交互性越多,这种技术割裂就越明显。
这种割裂不仅会损害开发人员的体验——用户体验也会受到影响。使用真正以 HTML 为中心且未充分利用 React 的工具,每个页面导航都会变成完整的页面重新加载,从而清除了所有客户端状态。如今,许多用户希望在应用内导航顺畅,而不是 90 年代风格的整页重新加载。同样,许多开发人员更喜欢使用单一的渲染模型而不是混合两种不同的模型来构建他们的应用。开发者想用 React 构建整个应用。
如果使用 React 构建整个应用,那么能够使用 SSG/SSR 很重要。在 Create React App 中缺乏对它们的支持。除此之外,经过多年的生态系统创新,React 的许多其他问题现在都有了成熟的解决方案。例如,network waterfalls 和 bundle 大小。
即使应用没有像面向内容的网站那样从 SSG 或 SSR 中获益,它也可能会受到网络瀑布的影响。如果在挂载时获取数据,则在加载所有代码并渲染组件之前,第一次数据获取甚至不会开始。这是一个 waterfall:如果应用知道如何在代码仍在加载时开始获取数据,那么就可以并行完成。在导航中,如果父组件和子组件都需要获取某些内容,则会产生更糟糕的 waterfall。当我们谈论 React 性能时,无法回避一个事实:对于如此多的应用来说,waterfall 是性能的瓶颈。要解决这些 waterfall,需要将数据获取与路由集成起来,而Create React App 无法做到这一点。
我们的应用代码会随着添加的每个新功能和额外依赖项而不断增长。如果经常部署,应用在每次使用时加载速度可能会变得非常慢,因为它总是需要加载所有代码。有几种方法可以解决这个问题;可以移动一些代码以在服务端或在构建期间运行(如果工具允许)。理想情况下,还可以按路由拆分代码。然而,如果尝试手动进行代码拆分,通常会使性能更差。要解决这一问题,需要将数据获取与路由和打包相结合,而 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 构建单个按钮,也可以使用它构建整个应用。可以使用它在已有 20 年历史的 Perl 网站中构建仪表板,或者可以使用 React 制作混合 SSG/SSR 的电子商务网站。这种灵活性是必不可少的,用户也很喜欢它。
React 团队也希望为完全使用 React 构建的新应用提供更好的默认设置。如果默认建议的创建 React 应用的方法支持 SSG 和 SSR、自动代码拆分、路由预加载、保留客户端 UI 状态的导航以及其他可实现出色用户体验的功能,就太好了。至少,创建 React 应用的默认建议方式不应该完全被排除在这些功能之外,因为现有的仅客户端架构没有实现这些功能。
React 面临着挑战,帮助 React 框架提供出色用户体验的最佳方式就是专注于 React 的底层。React 本身可以在渲染层做一些独特的事情,这些事情大大提高了框架在其他层的能力。例如,与<Suspense>一样,一个 React API 可以在幕后为框架解锁一系列框架优化。
React 是一个库,它提供了一些 API,可让定义和组合组件。React 也是一种架构,它提供了让框架作者充分利用其渲染模型的构建块。我们可以在没有框架的情况下使用 React。但需要确保,如果将它与框架一起使用,框架能够充分利用 React。在过去几年中构建的许多功能(<Suspense>、useTransition、流式 API(如 renderToPipeableStream 和实验性的服务端组件)都是面向框架的。它们让框架通过集成打包、路由和数据获取来充分利用 React。
可以看到,Next 13、Gatsby 5 和 Remix 1.11 中采用了其中一些功能。还有很多工作要做,其中一些工作正在从实验阶段结束。尽管如此,React 团队还是很高兴看到多年的努力得到了回报,并使 React 框架(及其用户)能够发布更快的应用。
一个库,多个框架
React 生态系统更适合拥有众多竞争,有多种竞争的数据获取解决方案和路由解决方案。这些选择每年都会变得更好。也有多种集成路由、数据获取、渲染和编译的解决方案——即多个 React 框架。
我们希望保持这种状态,也希望在可能的情况下鼓励融合并使 React 生态系统从中受益。例如,不同的框架可能使用不同的机制来加载数据。但是,如果它们都采用 <Suspense> 作为加载指示器,那么在 <Suspense> 中的更高级别的功能将适用于所有框架。
如果大多数 React 应用的最佳方式是从一个框架开始,那我们应该建议使用哪个框架?我们应该选择一个吗?我们如何决定选择哪一个?如果它随着时间的推移停滞不前怎么办?这就引出了上面提到的问题。
我们应该用 Create React App 做什么?
Create React App 最初的目标是:
- 提供一种无需配置即可启动新 React 项目的简单方法;
- 集成编译相关依赖,方便升级;
- 让 React 团队尽可能广泛地部署工具更新(例如快速刷新支持、Hooks lint 规则)。
然而,它不再满足最初的目标,即成为创建 React 应用的最佳方式。通过提高标准并将编译与渲染、路由和数据获取相集成,框架可以让用户创建 React 应用时:
- 充分利用 Web API 来默认提供快速的应用和网站,无论大小;
- 充分利用 React 及其框架级功能;
- 提供路由和数据获取。
React 生态系统值得推荐一种默认的方法,它可以充分利用 Web 和 React 本身。这甚至并不意味着一定要依赖于 Node.js 服务器。许多流行的框架不需要服务器并且可以在 SSG 模式下工作,因此它们也可以解决“完全静态”的用例。框架的好处就是,如果以后需要 SSR,不需要进行迁移,它和其他功能一样开箱即用(例如,Remix 提供了开箱即用的 mutation 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框架,但建议在大多数应用中使用其中一个框架。不利的一面就是,这将影响 React 的品牌(“为什么不推荐创建 React 应用?”)。
选项 4:让 Create React App 使用单一框架
可以选择一个指定框架,并更改 Create React App 以默认使用该框架创建应用。这种方法的主要问题是它使其他解决方案很难竞争——尤其是当它们的权衡略有不同,但在流行度、功能集和质量方面大致相同时。这种行为上的改变也是具有破坏性的,因为所有的旧教程都会以一种不明显的方式中断。
选项 5:将 Create React App 变成启动器
可以将 Create React App 保留为命令,但将其变成启动器。它将建议一个推荐框架列表,然后是“经典”无框架方法。“经典”方法将产生一个像 CRA 现在这样的客户端专用应用(以避免破坏已有教程),但内部最终可能会使用 Vite。
要想进入精选框架列表,React 框架必须满足特定条件。需要考虑社区的流行度和采用率(以保持列表简短)、功能集、性能特征、充分利用 Web 平台和 React 本身的能力、它是否得到积极维护以及是否清楚如何在各种托管服务和环境中托管它。每个框架的入门模板将由 React 团队维护,以确保它们具有一致的设计和品牌,不链接到商业服务,并且结构相似。需要向社区清楚地传达是如何做出这些选择的,并且会定期重新评估它们。
React 团队的建议
React 团队目前倾向于选项 5(“将 Create React App 变成启动器”)。Create React App 的最初目标是为大多数 React 用户提供启动新的 React web 应用的最佳方式。重新调整它的用途,启动器明确传达了我们认为最适合大多数新 Web 应用的转变。与选项 3 不同,它避免了“创建一个 React 应用”在某种程度上被弃用的看法。
React 团队将制定更详细的 RFC 提案,以充实这些要点。同时,希望听到对这些问题的更多反馈。
对于使用 Vite 替换 Create React App,你有什么看法?欢迎在评论区分享~
参考:https://github.com/reactjs/reactjs.org/pull/5487#issuecomment-1409720741