提取器
Extractors
我们在上一章中看到的服务器函数展示了如何在服务器上运行代码,并将其与你在浏览器中渲染的用户界面集成。但它们并没有展示如何真正发挥服务器的全部潜力。
The server functions we looked at in the last chapter showed how to run code on the server, and integrate it with the user interface you’re rendering in the browser. But they didn’t show you much about how to actually use your server to its full potential.
服务器框架
Server Frameworks
我们称 Leptos 为“全栈”框架,但“全栈”总是一个误称(毕竟它从来不意味着从浏览器到你的电力公司的所有环节)。对我们来说,“全栈”意味着你的 Leptos 应用可以在浏览器中运行,也可以在服务器上运行,并能将两者集成,汇聚各方独有的特性;正如我们在本书中目前所看到的,浏览器上的一个按钮点击可以驱动服务器上的数据库读取,而这两者都写在同一个 Rust 模块中。但 Leptos 本身并不提供服务器(或数据库、操作系统、固件、电缆……)。
We call Leptos a “full-stack” framework, but “full-stack” is always a misnomer (after all, it never means everything from the browser to your power company.) For us, “full stack” means that your Leptos app can run in the browser, and can run on the server, and can integrate the two, drawing together the unique features available in each; as we’ve seen in the book so far, a button click on the browser can drive a database read on the server, both written in the same Rust module. But Leptos itself doesn’t provide the server (or the database, or the operating system, or the firmware, or the electrical cables...)
相反,Leptos 为两个最流行的 Rust Web 服务器框架提供了集成:Actix Web (leptos_actix) 和 Axum (leptos_axum)。我们已经与每个服务器的路由管理器构建了集成,这样你就可以通过 .leptos_routes() 简单地将 Leptos 应用插入到现有的服务器中,并轻松处理服务器函数调用。
Instead, Leptos provides integrations for the two most popular Rust web server frameworks, Actix Web (leptos_actix) and Axum (leptos_axum). We’ve built integrations with each server’s router so that you can simply plug your Leptos app into an existing server with .leptos_routes(), and easily handle server function calls.
If you haven’t seen our Actix and Axum templates, now’s a good time to check them out.
使用提取器
Using Extractors
Actix 和 Axum 的处理器(handlers)都建立在同一个强大的概念之上:提取器 (extractors)。提取器从 HTTP 请求中“提取”类型化的数据,让你能够轻松访问服务器特定的数据。
Both Actix and Axum handlers are built on the same powerful idea of extractors. Extractors “extract” typed data from an HTTP request, allowing you to access server-specific data easily.
Leptos 提供了 extract 辅助函数,让你可以直接在服务器函数中使用这些提取器,其语法非常类似于每个框架的处理器,非常方便。
Leptos provides extract helper functions to let you use these extractors directly in your server functions, with a convenient syntax very similar to handlers for each framework.
Actix 提取器
Actix Extractors
leptos_actix 中的 extract 函数 接收一个处理函数作为参数。该处理函数遵循类似于 Actix 处理器的规则:它是一个异步函数,接收将从请求中提取的参数,并返回某些值。该处理函数接收提取的数据作为其参数,并可以在 async move 块的主体内部对它们执行进一步的 async 操作。它会将你返回的任何值返回到服务器函数中。
The extract function in leptos_actix takes a handler function as its argument. The handler follows similar rules to an Actix handler: it is an async function that receives arguments that will be extracted from the request and returns some value. The handler function receives that extracted data as its arguments, and can do further async work on them inside the body of the async move block. It returns whatever value you return back out into the server function.
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct MyQuery {
foo: String,
}
#[server]
pub async fn actix_extract() -> Result<String, ServerFnError> {
use actix_web::dev::ConnectionInfo;
use actix_web::web::Query;
use leptos_actix::extract;
let (Query(search), connection): (Query<MyQuery>, ConnectionInfo) = extract().await?;
Ok(format!("search = {search:?}\nconnection = {connection:?}",))
}
Axum 提取器
Axum Extractors
leptos_axum::extract 函数的语法非常相似。
The syntax for the leptos_axum::extract function is very similar.
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct MyQuery {
foo: String,
}
#[server]
pub async fn axum_extract() -> Result<String, ServerFnError> {
use axum::{extract::Query, http::Method};
use leptos_axum::extract;
let (method, query): (Method, Query<MyQuery>) = extract().await?;
Ok(format!("{method:?} and {query:?}"))
}
这些是从服务器访问基本数据的相对简单的例子。但你可以使用完全相同的 extract() 模式,通过提取器访问诸如 header、cookie、数据库连接池等内容。
These are relatively simple examples accessing basic data from the server. But you can use extractors to access things like headers, cookies, database connection pools, and more, using the exact same extract() pattern.
Axum 的 extract 函数仅支持状态(state)为 () 的提取器。如果你需要一个使用 State 的提取器,你应该使用 extract_with_state。这要求你提供状态。你可以通过使用 Axum 的 FromRef 模式扩展现有的 LeptosOptions 状态来实现,这在渲染期间以及带有自定义处理器的服务器函数中通过上下文(context)提供状态。
The Axum extract function only supports extractors for which the state is (). If you need an extractor that uses State, you should use extract_with_state. This requires you to provide the state. You can do this by extending the existing LeptosOptions state using the Axum FromRef pattern, which providing the state as context during render and server functions with custom handlers.
use axum::extract::FromRef;
/// 派生 FromRef 以允许状态中包含多个项目,
/// 使用 Axum 的 SubStates 模式。
/// Derive FromRef to allow multiple items in state, using Axum’s
/// SubStates pattern.
#[derive(FromRef, Debug, Clone)]
pub struct AppState{
pub leptos_options: LeptosOptions,
pub pool: SqlitePool
}
Click here for an example of providing context in custom handlers.
Axum 状态 (State)
Axum State
Axum 典型的依赖注入模式是提供一个 State,然后可以在你的路由处理器中提取它。Leptos 通过上下文(context)提供了它自己的依赖注入方法。上下文通常可以用来代替 State 来提供共享的服务器数据(例如,数据库连接池)。
Axum's typical pattern for dependency injection is to provide a State, which can then be extracted in your route handler. Leptos provides its own method of dependency injection via context. Context can often be used instead of State to provide shared server data (for example, a database connection pool).
let connection_pool = /* 这里是某些共享状态 */;
let app = Router::new()
.leptos_routes_with_context(
&leptos_options,
routes,
move || provide_context(connection_pool.clone()),
{
let leptos_options = leptos_options.clone();
move || shell(leptos_options.clone())
},
)
// 等等。
然后,可以在你的服务器函数内部通过简单的 use_context::<T>() 访问此上下文。
This context can then be accessed with a simple use_context::<T>() inside your server functions.
如果你 需要 在服务器函数中使用 State——例如,如果你有一个现有的 Axum 提取器需要 State——通过 Axum 的 FromRef 模式和 extract_with_state 也是可能的。本质上,你需要同时通过上下文和 Axum 路由状态提供该状态:
If you need to use State in a server function—for example, if you have an existing Axum extractor that requires State—that is also possible using Axum's FromRef pattern and extract_with_state. Essentially you'll need to provide the state both via context and via Axum router state:
#[derive(FromRef, Debug, Clone)]
pub struct MyData {
pub value: usize,
pub leptos_options: LeptosOptions,
}
let app_state = MyData {
value: 42,
leptos_options,
};
// 使用路由构建我们的应用
// build our application with a route
let app = Router::new()
.leptos_routes_with_context(
&app_state,
routes,
{
let app_state = app_state.clone();
move || provide_context(app_state.clone())
},
App,
)
.fallback(file_and_error_handler)
.with_state(app_state);
// ...
#[server]
pub async fn uses_state() -> Result<(), ServerFnError> {
let state = expect_context::<MyData>();
let SomeStateExtractor(data) = extract_with_state(&state).await?;
// todo
}
泛型状态 (Generic State)
Generic State
在某些情况下,你可能希望在状态中使用泛型。让我们使用以下示例:
In some cases, you may want to use a generic type for your state. Let's use the following example:
pub struct AppState<TS: ThingService> {
pub thing_service: Arc<TS>,
}
在 Axum 中,你通常会在处理器中使用泛型参数,如下所示:
In Axum you would typically use a generic parameter with your handler, like so:
pub async fn do_thing<TS: ThingService>(
State(state): State<AppState<TS>>,
) -> Result<(), ThingError> {
state.thing_service.do_thing()
}
不幸的是,Leptos 服务器函数目前不支持泛型参数。但是,你可以通过在服务器函数中使用具体类型来调用内部泛型函数来规避此限制。你可以这样做:
Unfortunately, generic parameters are not currently supported in Leptos server functions. However, you can work around this limitation by using a concrete type in your server function to call an inner generic function. Here's how you can do it:
pub async do_thing_inner<TS: ThingService>() -> Result<(), ServerFnError> {
let state = expect_context::<AppState<TS>>(); // 可以工作!
state.thing_service.do_thing()
}
#[server]
pub async do_thing() -> Result<(), ServerFnError> {
use crate::thing::service::Service as ConcreteThingService;
use crate::thing::some_dep::SomeDep;
do_thing_inner::<ConcreteThingService<SomeDep>>().await
}
关于加载数据模式的说明
A Note about Data-Loading Patterns
由于 Actix 和(尤其是)Axum 建立在单次往返 HTTP 请求和响应的思想之上,你通常在应用程序的“顶部”(即开始渲染之前)运行提取器,并使用提取的数据来决定应如何渲染。在渲染一个 <button> 之前,你已经加载了应用可能需要的所有数据。并且任何给定的路由处理器都需要知道该路由需要提取的所有数据。
Because Actix and (especially) Axum are built on the idea of a single round-trip HTTP request and response, you typically run extractors near the “top” of your application (i.e., before you start rendering) and use the extracted data to determine how that should be rendered. Before you render a <button>, you load all the data your app could need. And any given route handler needs to know all the data that will need to be extracted by that route.
但 Leptos 集成了客户端和服务器,因此能够在不强制重新加载所有数据的情况下,使用来自服务器的新数据刷新 UI 的一小部分是很重要的。所以 Leptos 喜欢将数据加载在应用中“下沉”,尽可能靠近用户界面的叶子节点。当你点击一个 <button> 时,它可以只刷新它需要的数据。这正是服务器函数的用武之地:它们为你提供了对要加载和重新加载的数据的粒度访问。
But Leptos integrates both the client and the server, and it’s important to be able to refresh small pieces of your UI with new data from the server without forcing a full reload of all the data. So Leptos likes to push data loading “down” in your application, as far towards the leaves of your user interface as possible. When you click a <button>, it can refresh just the data it needs. This is exactly what server functions are for: they give you granular access to data to be loaded and reloaded.
extract() 函数通过在服务器函数中使用提取器,让你能够结合这两种模型。你可以获得路由提取器的全部能力,同时将需要提取什么内容的知识分散到各个组件中。这使得重构和重新组织路由变得更加容易:你不需要预先指定一个路由所需的所有数据。
The extract() functions let you combine both models by using extractors in your server functions. You get access to the full power of route extractors, while decentralizing knowledge of what needs to be extracted down to your individual components. This makes it easier to refactor and reorganize routes: you don’t need to specify all the data a route needs up front.