页面加载的生命周期
在我们深入细节之前,有一个更高层次的概述可能会有所帮助。从您输入服务器渲染的Leptos应用程序的URL的那一刻,到您点击按钮并且计数器增加的那一刻,到底发生了什么?
我在这里假设您对互联网如何工作有一些基本知识,不会深入HTTP或其他细节。相反,我将尝试展示Leptos API的不同部分如何映射到过程的每个部分。
这个描述也从您的应用程序正在为两个单独的目标编译的前提开始:
- 服务器版本,通常在Actix或Axum上运行,使用Leptos
ssr功能编译 - 浏览器版本,使用Leptos
hydrate功能编译为WebAssembly(WASM)
cargo-leptos构建工具存在于协调为这两个不同目标编译应用程序的过程。
在服务器上
- 您的浏览器向您的服务器发出该URL的
GET请求。此时,浏览器对即将渲染的页面几乎一无所知。("浏览器如何知道在哪里请求页面?"这是一个有趣的问题,但超出了本教程的范围!) - 服务器接收该请求,并检查它是否有处理该路径上
GET请求的方法。这就是leptos_axum和leptos_actix中的.leptos_routes()方法的用途。当服务器启动时,这些方法遍历您在<Routes/>中提供的路由结构,生成应用程序可以处理的所有可能路由的列表,并告诉服务器的路由器"对于这些路由中的每一个,如果您收到请求...将其交给Leptos。" - 服务器看到这个路由可以由Leptos处理。所以它渲染您的根组件(通常称为
<App/>之类的东西),为其提供正在请求的URL和一些其他数据,如HTTP标头和请求元数据。 - 您的应用程序在服务器上运行一次,构建将在该路由渲染的组件树的HTML版本。(在下一章中,关于资源和
<Suspense/>还有更多要说的。) - 服务器返回此HTML页面,还注入有关如何加载已编译为WASM的应用程序版本的信息,以便它可以在浏览器中运行。
返回的HTML页面本质上是您的应用程序,"脱水"或"冻干":它是没有您添加的任何响应性或事件监听器的HTML。浏览器将通过添加响应式系统并将事件监听器附加到该服务器渲染的HTML来"重新水合"此HTML页面。因此,适用于此过程两半的两个功能标志:服务器上的
ssr用于"服务器端渲染",浏览器中的hydrate用于重新水合过程。
在浏览器中
- 浏览器从服务器接收此HTML页面。它立即回到服务器开始加载运行应用程序的交互式客户端版本所需的JS和WASM。
- 与此同时,它渲染HTML版本。
- 当WASM版本重新加载时,它执行与服务器相同的路由匹配过程。因为
<Routes/>组件在服务器和客户端中是相同的,浏览器版本将读取URL并渲染与服务器已经返回的相同页面。 - 在这个初始"水合"阶段,应用程序的WASM版本不会重新创建构成应用程序的DOM节点。相反,它遍历现有的HTML树,"拾取"现有元素并添加必要的交互性。
请注意,这里有一些权衡。在此水合过程完成之前,页面将_看起来_是交互式的,但实际上不会响应交互。例如,如果您有一个计数器按钮并在WASM加载之前点击它,计数不会增加,因为必要的事件监听器和响应性尚未添加。我们将在未来的章节中查看一些构建"优雅降级"的方法。
客户端导航
下一步非常重要。想象用户现在点击链接导航到应用程序中的另一个页面。
浏览器_不会_再次往返服务器,重新加载整个页面,就像在普通HTML页面之间导航或使用服务器渲染(例如使用PHP)但没有客户端部分的应用程序一样。
相反,应用程序的WASM版本将在浏览器中加载新页面,而不从服务器请求另一个页面。本质上,您的应用程序将自己从服务器加载的"多页应用程序"升级为浏览器渲染的"单页应用程序"。这产生了两全其美的效果:由于服务器渲染的HTML而快速的初始加载时间,以及由于客户端路由而快速的二次导航。
以下章节中将描述的一些内容——如服务器函数、资源和<Suspense/>之间的交互——可能看起来过于复杂。您可能会发现自己在问,"如果我的页面在服务器上渲染为HTML,为什么我不能在服务器上.await这个?如果我可以在服务器函数中调用库X,为什么我不能在组件中调用它?"原因很简单:为了启用从服务器渲染到客户端渲染的升级,应用程序中的所有内容都必须能够在客户端和服务器上运行。
当然,这不是创建网站或Web框架的唯一方法。但这是最常见的方法,我们恰好认为这是为用户创造最流畅体验的相当好的方法。