前端未来的航向

原文:https://frontendmastery.com/posts/navigating-the-future-of-frontend/

理解现代前端元框架。连接起新旧基本概念之间的关联。

介绍

前端生态系统处于转型期。新兴的前端开发者在竞争激烈的框架、概念、倡导者、偏好和最佳实践的复杂环境中前行。

今天,网络和网络技术运行着大多数被使用的软件。

从Java小程序、Flash,到Javascript的快速发展,无数工具被构建用于管理浏览器、屏幕尺寸、设备能力、网络条件和不断增长的用户期望的广泛范围。

但是在追求一个完整的软件分发平台的过程中,网络的基本约束并没有改变。

在本文中,我们将探讨现代Javascript元框架如何应对这些约束。

我们将构建一个高层次的心智模型,来理解编译、路由、数据加载和变更、缓存以及失效等基本要素如何结合在一起。这将帮助我们理解新兴的架构模式,包括React Server组件提供的功能。

尽管魔鬼在细节中,我们会看到更多的框架之间的趋同性,而这可能并不明显。

最后,我们将拥有超越框架崛起和衰落以及表面级API的基本概念,并更好地理解前端生态系统的发展方向。

框架的兴衰

网络平台是一个缓慢发展的全面层,位于建立在其之上的用户界面工具之下。随着时间的推移,平台会改进,消除对更高级抽象的需求。

但是平台并不总是提供必要的功能。Flash的消亡使得Javascript生态系统填补了空白,成为构建丰富交互体验的首选技术。

过去十年的技术繁荣使得许多组织将网络作为主要的面向客户的分发渠道,创造了类似于传统桌面开发的前端专业化,与以往的网页开发者相比。

如今的流行框架,如Angular、React、Vue、Svelte、Solid、Qwik等,在解决客户端交互的同时,能够组合渲染组件,以便数据变化时呈现一致的外观。

细节导致不同的权衡,因此它们仍在辩论中,例如处理反应性的最佳方式、偏好于模板还是函数、或对复杂性的胃口。但是从高层来看,它们在类似的概念上趋同,并提供类似的功能。

一个重要的主题是元框架的不断上升的复杂性,它们在其之上构建应用级框架。让我们从理解两个普遍的循环开始我们的调查。

能力与适用性

工具的能力是表达思想的能力。随着它获得新的能力,工具变得更加复杂。

工具的适用性是指它是否适合在特定情况下使用。在组织环境中,这意味着满足尽可能多的人,而不经常遇到困难或问题。

增加能力的创新循环也会导致复杂性和混乱。这导致了构建抽象化的循环,限制了能力,或使其更容易使用。通常会出现对抗趋势,倾向于更简单的方法。

捆绑非捆绑

Javascript疲劳的现象源于不得不在多种技术之间进行选择,并使它们协同工作。

捆绑是将这些能力连接到单一产品中,使其更易于使用。

捆绑的抽象必须解决许多问题。因此,它们往往变得庞大,并具有抽象和魔术层,这常常使开发人员感到不舒服。

当这些被视为过于缓慢或限制时,新的循环开始了,其中一些部分被分离出来,进行独立创新。

通过这种持续迭代和随着时间的推移经历痛点的痛苦,我们学会了什么是重要的,什么是不重要的。

多年来,客户端组件框架的迭代使得每个框架都拥有自己的“元”框架,将分散的工具和最佳实践捆绑在一起。React的许多新API都是为了集成到这些更高级别的应用框架中而解除捆绑的能力。

回归基础

”如果你广泛了解一种方式,你就会在一切中看到它” - 宫本武藏

计算机系统的两个主要组成部分是计算和存储。

系统的性能受到计算操作和I/O(输入、输出)操作的限制,例如从存储中获取数据。

在网络上,这些基本约束表现为Javascript的单线程和用户与服务器之间的网络延迟。

网络上的所有连接都涉及到遵循请求-响应模式的客户端和服务器。服务器始终是首选(即使它正在从CDN提供静态文件)。我们如何在服务器和客户端之间分配这些操作会导致不同的权衡。

在客户端进行计算可以实现快速交互,但如果过多,主线程将变得无响应。服务器没有这个限制,但要求它们执行操作会产生网络延迟。在网络上进行交互体验时,平衡这些约束至关重要。

读取和写入

另一个重要的要素是能够读取和写入数据。

网络最初是只读的静态文档。最终,我们开始持久化数据并动态生成HTML。借助可靠的 form 元素,我们现在可以执行写入操作,扩展了网络对新类型应用程序的能力。

在这个模型中,客户端上的用户交互转换为 同步 的HTTP请求。写入后体验的更新是 粗粒度 的,因为服务器响应的是一个全新生成的文档,浏览器会重新加载以显示它。

最终,我们得到了 XMLHttpRequest,它开启了更多的功能。用户操作现在可以是 异步 的,其中更新是 细粒度 的,仅更新页面的相关部分。

在这两种情况下,渲染的真实来源和应用程序状态都是 由服务器驱动 的。

到目前为止,我们对这个故事已经很熟悉了。随着时间的推移,模板和应用程序状态越来越多地转移到了客户端,其中应用程序状态变得 由客户端驱动 ,允许快速的乐观写入来掩盖底层网络。

这是许多过去十年进入这个行业的人所熟悉的主流模型。在一个速度至上的行业中,随着功能的增长,所有代码只有一个去处。

当使用单一机器的方法受到性能限制时,我们可以放弃它;否则,我们就进入了分布式系统的领域。

分布式前端系统

客户端唯一方法的心智模型就像是一个长时间运行的桌面应用程序,以异步方式与后端同步。

转变为服务器驱动的应用程序状态是最重要的心智模型变化之一,因为大多数“前端后端”代码都转移到了服务器端。

与其他生态系统中的其他服务器优先应用程序框架的区别在于,保持了丰富的客户端交互能力和稳定的导航能力。

混淆的是如何在同一框架和产品中利用服务器驱动模式的性能特征和客户端驱动方法的能力,以及何时以及如何做到这一点。

React Server组件进一步发展,追求统一的组件构建体验,这些组件交织在服务器和客户端之间。这是行业最主流的框架的一个重大转变。

其他语言生态系统正在探索类似的概念。例如,C#的 Blazor、Clojure的 Electric 和 Rust的 Leptos 都追求类似的理念。

在我们陷入细节之前,让我们退后一步,了解为什么现在这样?

除了更好的性能,让我们了解一些导致我们在Javascript生态系统中朝着这个新方向发展的关键因素。

  • 一种语言统治一切

    作为网络的通用语言,没有其他语言像JavaScript那样普遍

    当Node.js出现时,我们可以编写在客户端和服务器上都运行的同构代码。早期的先锋全栈框架,如 Meteor 采用了这些能力。

    Meteor是一个早期的同构Javascript框架的示例,具有全栈响应性和 RPCs,它抽象了浏览器和服务器是非常不同的环境的事实。

    当时,这种一站式方法在行业中失去了较少意见和更宽松方法(如React作为最小视图库)的心智份额。

    从那时起,TypeScript 产生了巨大影响,成为许多开发人员和组织的事实标准。

    tPRCT3 stack 这样的工具使用同构的Typescript提供端到端的类型安全性,统一了代码、类型、模式和 执行模型 ,它们位于同一仓库中。

  • 下一代编译器和打包工具

    我们可以将编译器视为将我们编写的代码转换、准备和优化以便以后执行的程序。我们在大规模构建和交付前端中探讨了这在网络上的作用。

    打包工具和编译器技术的稳步进步导致了从头开始重写的快速 下一代打包工具 的迭代,这些工具非常擅长管理Javascript模块图。

    这些能力使得框架能够分离客户端和服务器的模块图,这些模块图在不同的运行时中执行。

    这种代码提取的想法支持统一的客户端-服务器构建体验,以及需要在后台进行的许多神奇操作。

  • Suspense 的出现

    了解 Suspense 解锁的功能对于理解正在出现在React框架中的服务器优先心智模型至关重要。其他框架中也正在探索类似的变体,如 SolidVuePreactAstro

    用户体验的角度来看,关键的洞察力在于我们可以更有意识地设计数据密集体验的加载阶段。

    性能的角度来看,Suspense 提供了资源加载和渲染的并行化。

    受 Facebook 的 BigPipe 概念的启发,它减轻了等待服务器获取数据并渲染HTML的同步等待时间,同时浏览器处于空闲状态。

    客户端可以在遇到这些标签时开始下载资源,如字体、CSS 和 JS,当浏览器解析HTML时。与此同时,服务器正在并行加载数据。

    这减轻了纯粹由服务器驱动的“获取然后渲染”模型中的对 TTFB 和较慢的 最大内容渲染 的影响。

    但与简单地提前刷新 <head> 并从客户端异步加载所有内容相比,这可以通过对分阶段加载阶段进行细粒度控制来实现。与数据和代码在瀑布式加载中加载并导致累积布局转移的大量未经请求的 throbbers 在页面中“爆米花般”出现和消失形成对比。

    除了初始页面加载之外,它还允许RSC(React Server Components)流式传输序列化的虚拟DOM以进行就地转换。从技术的角度来看,这里的洞察力是,当渲染是异步的并且可以无序流式传输时,Suspense解决了一致渲染的问题。

    更多关于这个概念的内容:

    框架的响应系统解决的一个核心问题是如何确保用户界面在数据随时间变化时保持一致地渲染。

    一致性意味着显示的内容准确反映当前的真实数据源。它确保UI不显示过时或与使用相同数据的另一个元素不同的数据。

    虚拟DOM和信号都是解决这个问题的方法。对这两种方法的简化理解是,虚拟DOM是粗粒度的,因为它“比较视图”,而信号是细粒度的,并“比较模型”。每种方法都有不同的权衡。

    Suspense 从另一个角度解决了渲染一致性问题,当组件树通过网络加载时,资源被异步加载为I/O绑定操作时。

    这意味着我们可以从不同的数据源流式传输响应,而无需手动管理竞争条件,并且在事物以无序方式通过网络到达时,交换占位符DOM内容以获取最终的DOM内容。

    它还可以与构建时编译器巧妙结合,创建新的渲染方法,如部分预渲染

  • 基础设施的进步

    虽然这些能力在JavaScript生态系统中一直在酝酿,但驱动Web的云基础设施也在迅速发展。

    从服务器运行时流式传输到浏览器需要具备此能力的后端基础设施。

    许多服务器基础设施提供商现在都支持这种能力。

一路上的路由器

“你必须明白,登山的路不止一条。” - 宫本武藏

路由在整个堆栈中都是基础性的。互联网和网络可以被看作是一系列路由器。路由器也是任何框架的支柱。它是编译器在构建时的第一个入口点,对于初始请求以及之后的许多用户交互,它也是目的地。

URL(和QR码)的便利性和共享性对于网络作为软件分发机制的成功至关重要。路由器是将URL连接到需要加载的代码和数据之间的连接器。

一个思考路由器的方式是将其视为URL的状态管理器,而路由则是应用程序中的一个可共享的目的地。这是一项重要的工作,因为URL是决定显示什么布局以及需要加载什么代码和数据的主要输入。

路由器与数据获取和缓存、突变和重新验证等关键操作相关联。因此,应用程序路由器所在的位置以及它所做的工作对于前端架构至关重要。

客户端与服务器

在传统的服务器驱动方法中,路由器将请求映射到获取数据并呈现HTML模板的URL。URL之间的转换导致生成一个新文档并需要刷新浏览器。

在客户端驱动方法中,路由器的代码必须下载到浏览器中,在一切都初始化后,它开始监听浏览器历史记录的更改:例如链接点击和前进后退导航事件。

从这里开始,它不是请求一个全新的文档,而是将URL的变化映射到重新呈现现有文档的客户端组件代码。

客户端路由对于单页应用程序(SPA)架构至关重要。路由转换保留了客户端的当前状态,现有的JS、CSS和其他资产无需重新评估。在进行过渡之前,代码和数据可以进行预加载。它还使得在路由转换之间的体验,例如动画效果成为可能(类似的UX模式现在已经被整合到了平台中)。

大多数元框架提供了服务器驱动的应用程序状态与保留客户端路由的总体功能的组合。客户端和服务器之间的区别开始变得模糊,采用Qwik和RSC架构的方法更是如此。

瀑布的历史

有一段时间,动态客户端路由是一种常见的模式。也就是说,将路由器渲染为树中的任何位置的组件。

这种设计允许在运行时具有高度灵活和动态的功能,例如渲染<Redirect />组件。然而,为了知道要加载什么代码和数据,我们必须渲染组件树以确定路由。

实际上,这意味着许多采用这种模式的客户端驱动组件架构会遇到相当数量的客户端-服务器网络瀑布。

瀑布是一系列顺序网络请求,每个请求都依赖于前一个请求的完成。瀑布是潜藏在网络标签中的性能潜在杀手。

元框架路由器都收敛于静态定义的路由定义

这种的一个流行的体现形式是基于文件系统的路由 - 将文件夹结构映射到URL,然后将URL映射到这些文件夹中的特定文件的直观方式。编译器通过遍历文件系统生成路由。

使用配置文件定义所有路由是另一种简单且类型安全的方法。

这些路由定义自然地形成了一个分层树结构。大多数路由器创建一个将URL段映射到相应组件子树的嵌套路由树。接下来我们将看到为什么这一点很重要,以及如何利用URL对许多首先服务器数据加载模式至关重要。

瀑布的历史

有一段时间,动态客户端路由是一种常见的模式。也就是说,将路由器渲染为树中的任何位置的组件。

这种设计允许在运行时具有高度灵活和动态的功能,例如渲染<Redirect />组件。然而,为了知道要加载什么代码和数据,我们必须渲染组件树以确定路由。

实际上,这意味着许多采用这种模式的客户端驱动组件架构会遇到相当数量的客户端-服务器网络瀑布。

瀑布是一系列顺序网络请求,每个请求都依赖于前一个请求的完成。瀑布是潜藏在网络标签中的性能潜在杀手。

元框架路由器都收敛于静态定义的路由定义

这种的一个流行的体现形式是基于文件系统的路由 - 将文件夹结构映射到URL,然后将URL映射到这些文件夹中的特定文件的直观方式。编译器通过遍历文件系统生成路由。

使用配置文件定义所有路由是另一种简单且类型安全的方法。

这些路由定义自然地形成了一个分层树结构。大多数路由器创建一个将URL段映射到相应组件子树的嵌套路由树。接下来我们将看到这一点为什么很重要,以及如何利用URL对许多首先服务器数据加载模式至关重要。

前端的新后端

在将URL映射到组件树之后,我们需要加载代码和数据。正如我们所见,元框架的一个重要主题是在不放弃组件化的丰富客户端方法的情况下协调服务器驱动的应用程序状态带来的性能优势。

共同定位是组件模型及其轻松组合的重要部分。在这里存在一个权衡,即在处理其自身数据依赖性的组件的可移植性之间,例如通过获取其自己的数据,与当组合时可能产生不必要的瀑布时的权衡。或者接受数据作为prop(或一个promise),并将其提升到路由级别进行获取。

我们已经谈到了Relay是一个高度可靠的“后端前端”客户端库的示例。允许在组件中共同定位数据,但带有提升的获取。这种能力带来了复杂性和捆绑大小的代价,并且需要GraphQL。让我们了解一下在移动到服务器时如何解决这些权衡,而无需捆绑客户端获取库或使用GraphQL。

永远的最佳伙伴

前端的后端(BFFs)是服务导向的后端环境中熟悉的设计模式

基本思想是定制的后端服务直接位于每个客户端平台(Web、移动、CLI等)的后面,并满足该前端应用程序的特定需求。

例如,在像HTMX/Turbo这样的服务器驱动方法中,后端响应的是HTML部分,以供执行AJAX样式的更新的轻客户端使用。

对于RSCs,这是一个定制的后端,它返回序列化的组件树给一个轻客户端,或者是一个正在节食的厚客户端,具体取决于体验。

让我们了解与在服务器上运行此层相比的一些好处。

  • 简化客户端捆绑包,通过在服务器上保留大部分数据获取和数据转换逻辑,包括重型转换库(日期格式化、国际化等),以及将任何令牌或机密信息排除在发送到浏览器的代码之外。

  • 组合多个数据需求并修剪数据,避免过度获取。就像我们在Suspense中看到的那样,较慢的API调用可以流式传输到悬停边界中,而不会阻止呈现。

  • 赋予前端开发人员权力,使其具有类似于GraphQL解决方案的DX,允许产品开发人员为体验指定精确的数据需求。

  • 利用URL状态 - 反应性系统中状态管理的黄金法则是存储状态的最小表示,并使用它来推导出其他状态。

    我们可以将这个原则应用到URL中,其中单个URL段映射到组件子树,以及其中组件的当前状态。例如,使用与当前搜索过滤器或当前选定选项相对应的查询参数。

    从性能的角度来看,以这种方式管理状态允许此应用层尽可能接近服务器获取所有代码和数据。

    因此,在大多数情况下,当我们在客户端运行时,我们提前获得了所有需要的信息,而无需从客户端返回服务器请求。这对于初始加载和后续转换都是一个很好的位置。

    这也意味着我们充分利用了作为分发机制的Web的优势 - 通过URL提供的可共享链接,提供了一致的视图,并确保当您分享它时,其他人也可以看到选定的过滤器、打开的模型等。

    考虑到这一点,URL是存储某些类型的客户端状态的好地方,如果它允许您提前获取代码或数据的话。

缓存主导一切

将这些层移到客户端之外意味着我们可以在服务器上做更多事情并缓存更多内容。性能的基本原则之一是减少工作量。实现这一点的一种方法是尽可能多地提前完成尽可能多的工作,并将结果存储在缓存中。

在高层次上理解多种类型的缓存(以及缓存中的更深层)是很重要的。

  • 公共缓存存储工作结果,其中数据不敏感或个性化。一个例子是公共CDN,其中存储了服务器构建的HTML输出。

  • 私有缓存仅对单个用户(或不同用户群)可访问。一个例子是浏览器的原生HTTP缓存或浏览器的内存中客户端缓存远程数据。

在任何系统中,复杂性的一个主要来源是状态管理。在前端,其中相当大一部分是管理前端与其交互的远程数据的同步,这实际上是一种缓存管理

新的远程数据缓存

正如我们所见,将浏览器中的内存缓存作为视图的真实来源,可以实现乐观写入以获得快速的交互。每当我们有缓存时,我们需要了解它们是如何失效的。让我们检查与客户端缓存交互的不同方式。

  • 手动缓存管理:这涉及使用像Redux这样的状态管理工具手动管理规范化缓存,需要对乐观更新进行命令式的直接缓存更新,通常在响应返回时再次更新。

  • 基于键的失效消除了手动管理的需要。一个最佳实践工具的例子是React Query,它处理了一堆其他棘手的缓存管理问题。而Apollo或Relay采用了类似的方法,即一切都在幕后为您处理。

将此层移到服务器意味着将视图的主要来源移至服务器。在了解了在客户端模型中如何进行缓存管理后,让我们了解在服务器优先模型中如何进行缓存管理。

缓存失效和服务器操作

在“传统”的请求-响应模型中,更新服务器状态的写入与导航相关联,因为浏览器需要在更新后呈现新文档。一个典型的模式是POST、重定向、GET请求流程。

<!-- 浏览器将表单数据发送到“action”传递的URL -->
<form action="form_action.php" method="post">
  <!-- 字段 -->
</form>

大多数框架都采用了这种模式作为执行写入的默认起点。这使得渐进增强的SPA(PESPA)更容易实现。

表单的action属性接受一个端点的URL,该端点接收浏览器发送的表单数据。Remix和Sveltekit等框架将带有表单数据的写入发送到路由级别的服务器操作。而Next和SolidStart允许在组件树的任何位置调用服务器操作,使它们更类似于RPC。

一旦我们已经写入了服务器状态(数据库和任何服务器缓存),客户端框架就会使用其反应性系统来对比响应并在原地更新页面。

将数据编码为视图并返回,而不仅仅是数据的一个好处是,响应可以在单个服务器往返中返回更新的UI,而不是在接收到重定向后浏览器必须执行另一个GET以更新视图;这是React Server组件具有的优势,我们接下来将看到。

与手动管理客户端缓存相比,这种方法要简单得多,也不需要捆绑数据获取库。但正如我们之前所看到的,请求-响应模型在路由(或嵌套路由)级别上具有粗粒度的更新。

对于大多数体验来说,这是一个很好的默认选择。但是,对于某些功能,我们可能仍然需要细粒度缓存管理和客户端数据加载的好处。

例如,在轮询时,或者仅当粗粒度的请求-响应流程与您构建的内容不匹配,并且您希望避免在写入时重新运行服务器组件或loader函数时。

服务器操作可以在模块图中的任何位置使用,这使得您可以根据需要混合和匹配方法。例如,您可以使用服务器操作填充客户端缓存的结果。

// 使用RPC样式的服务器操作进行客户端获取和缓存
useQuery({
  queryKey: ['cool-beans'],
  // 返回承诺的任何函数
  queryFn: () => myServerActionThatReturnsJson(),
})

在这个领域还有很多微妙之处,我们需要更多时间来探讨。让我们最后了解一下React Server组件提供的一些新功能,以及它们与其他新兴技术的交集。

多维组件

React 服务器组件是一个重大的范式转变。在它们的初期阶段,它们很难理解,因为有许多不同的概念化方式。

从一个孤岛架构的角度来看,与 React 不同的各种服务器组件也在其他框架生态系统中被探索,比如Nuxt和Deno的Fresh

React所做的所有权衡都是为了保留组件模型及其所带来的组合能力。在架构层面理解它们的另一种方式是将它们视为组件化的后端前端

从客户端的角度来看,RSCs是已经运行过的组件,例如,在静态构建期间,或者在客户端运行之前在服务器上运行。

一个简单的思维模型是将它们视为序列化组件。将React从主线程中移出来通过序列化组件的输出来运行已经酝酿了一段时间

这种新的能力使得React能够表达多种架构风格:

提前构建的静态站点、采用HTMX风格的服务器驱动架构、逐步增强的SPA,或者是纯客户端渲染的SPA,只有一个入口点。或者在同一个应用程序中,这取决于具体的体验。除了潜在的性能优势之外,让我们探讨一下这种流畅架构的一些有趣的潜在好处。

  • 跨网络进行组合

    服务器组件提供了分享和组合全栈功能切片的能力,以及一种新的前端创作体验。

    在组织内部,这是对前端和后端团队分开模型的一种新理解,更符合以全栈垂直切片或钢丝为工作的团队的需求。

    对于具有标准化基础设施的大型组织来说,有一个强大的用例,即拥有可以被产品团队使用和组合的全栈平台组件。在federated model中组合RSCs的输出是另一种新兴的能力。

    这在生态系统级别将如何发展还不清楚,但它无疑会对组件API设计带来有趣的变化。例如,包可能还会导出preload函数,该函数可以被提升到路由级别,以避免服务器瀑布。因为这是一种新的范式,许多最佳实践仍然需要探索,还有很多陷阱等待被发现。

  • 服务器驱动的UI

    这是一种一些大型组织如AirBnbUber正在利用的概念,用于对原生移动前端的服务器驱动渲染进行细粒度控制。

    引入react-strict-dom提供了一种有趣的组合方式,可以更轻松地跨平台(不仅仅是Web)利用这些思想,包括新兴的平台,如AR和空间用户界面。

  • 生成式UI

    很难预测生成式AI在这个领域的未来将如何发展。但它已经来了,而且会留下来。在这种模型中新兴的能力是能够动态生成高度个性化的、丰富交互的体验。

    一个更现实的例子是在您知道要呈现哪些组件之前需要数据的情况下。在这种情况下,您需要提前打包多种不同类型的交互式组件。由于UI组件的数量可能会随着这种情况的增加而无限增长(例如CMS内容类型),否则这种动态组件的渲染将需要将所有代码发送到客户端,或者在从客户端延迟加载不同的组件类型时引入延迟。

    拥有端到端的组件意味着我们可以在不增加大型捆绑包的情况下流式传输组件。一个有趣的探索利用了AI函数调用以及服务器动作的灵活性来返回序列化的交互式组件。

前端的未来

我们在本文中再次涵盖了大量内容,但只是勉强触及了 Web 应用程序框架中的一些基本层面。更不用说像WebAssemblyWebGPU这样的技术可能会以意想不到的方式发挥作用。或者像Phoenix这样的其他生态系统如何在有状态的服务器方法本地优先开发方面做出不同的权衡。

处于所有这些技术前沿是令人兴奋的。然而,也很容易感到不知所措。

发展的一个重要技能是识别问题固有的复杂性,以及由于解决问题的解决方案而产生的偶然复杂性。对于前端的新人来说,这意味着将您的关注范围缩小到基础和不变的概念。

工程学(和生活)的一个重要部分是做出决策并承担方向。您对用户和团队需求了解得越多,就越能做出更好的权衡,对自己的决策越有信心。

了解技术演进的一个关键见解是,它并不总是对所有人都有进步意义。

在创新的一个阶段,某种能力或方法可能恰好符合您正在创建的东西;如果是这样,只需继续构建,愿原力与您同在。

参考资料和资源

2024-03-20

访问量 18

扫码关注公众号“前端微志”

第一时间获取新周刊

预览图片