Dioxus框架中全栈开发中的服务器函数(server functions)

531 阅读4分钟

与服务器通信

dioxus-fullstack提供了服务器函数,允许你从客户端调用服务器上自动生成的API,就像它是本地函数一样。

要创建一个服务器函数,只需在函数上添加#[server(YourUniqueType)]属性。该函数必须:

  • 是一个异步函数
  • 参数和返回类型都必须实现序列化和反序列化(使用serde)。
  • 返回一个Result,错误类型为ServerFnError

在启动服务器之前,你必须在主函数中对你的服务器宏中传入的类型调用register,以告诉Dioxus服务器函数的存在。

让我们继续构建我们在入门指南中制作的应用程序。我们将在我们的应用程序中添加一个服务器函数,允许我们在服务器上加倍计数。

首先,添加serde作为依赖项:

接下来,在你的main.rs中添加服务器函数:

#![allow(non_snake_case)]

use dioxus::prelude::*;

fn main() {
    launch(App)
}

fn App() -> Element {
    let mut count = use_signal(|| 0);

    rsx! {
        h1 { "High-Five counter: {count}" }
        button { onclick: move |_| count += 1, "Up high!" }
        button { onclick: move |_| count -= 1, "Down low!" }
        button {
            onclick: move |_| {
                async move {
                    if let Ok(new_count) = double_server(count()).await {
                        count.set(new_count);
                    }
                }
            },
            "Double"
        }
    }
}

#[server]
async fn double_server(number: i32) -> Result<i32, ServerFnError> {
    // 在服务器上执行一些昂贵的计算或访问数据库
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    let result = number * 2;
    println!("server calculated {result}");
    Ok(result)
}

现在,使用dx build --features web构建你的客户端捆绑包,并使用cargo run --features ssr运行你的服务器。你应该看到一个新按钮,将计数乘以2。

缓存数据获取

服务器函数的一个常见用例是从服务器获取数据:

#![allow(non_snake_case, unused)]

use dioxus::prelude::*;

fn main() {
    launch(app)
}

fn app() -> Element {
    let mut count = use_resource(get_server_data);

    rsx! {"server data is {count.value():?}"}
}

#[server]
async fn get_server_data() -> Result<String, ServerFnError> {
    // 访问数据库
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    Ok("Hello from the server!".to_string())
}

如果你导航到上述网站,你将首先看到server data is None,然后在WASM加载并完成对服务器的请求后,你将看到server data is Some(Ok("Hello from the server!"))

这种方法是可行的,但可能很慢。与其等待客户端加载并发送请求到服务器,不如我们能否在服务器上获取页面所需的所有数据,并将其与初始HTML页面一起发送到客户端?

这正是use_server_future钩子允许我们做的!use_server_futureuse_resource钩子类似,但它允许你在服务器上等待一个未来,并把未来结果发送到客户端。

让我们改变我们的数据获取方式,使用use_server_future

#![allow(non_snake_case, unused)]

use dioxus::prelude::*;

fn main() {
    launch(app);
}

fn app() -> Element {
    let mut count = use_server_future(get_server_data)?;

    rsx! {"server data is {count.value():?}"}
}

#[server]
async fn get_server_data() -> Result<String, ServerFnError> {
    // 访问数据库
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    Ok("Hello from the server!".to_string())
}

注意use_server_future后的?。这就是告诉Dioxus全栈在继续渲染之前等待未来解决。如果你想不为特定未来等待,可以去掉?并手动处理Option

现在当你加载页面时,你应该看到server data is Ok("Hello from the server!")。不需要等待WASM加载或等待请求完成!

使用dioxus-desktop运行客户端

到目前为止的项目使得Web浏览器与服务器交互,但也可以以类似的方式使桌面程序与服务器交互。(完整的示例代码可在Dioxus仓库中找到)

首先,我们需要创建两个二进制目标,一个用于桌面程序(client.rs文件),一个用于服务器(server.rs文件)。客户端应用程序和服务器函数写在共享的lib.rs文件中。

桌面和服务器目标的构建配置略有不同,以启用额外的依赖项或功能。完整的示例中的Cargo.toml有更多的信息,但主要点是:

  • client.rs必须使用desktop功能运行,以便包含可选的dioxus-desktop依赖项
  • server.rs必须使用ssr功能运行;这将生成服务器函数的服务器部分,并将运行我们的后端服务器。

创建项目后,你可以使用以下命令运行服务器可执行文件:

cargo run --bin server --features ssr

和客户端桌面可执行文件:

cargo run --bin client --features desktop

客户端代码

客户端文件非常简单。你只需要在客户端代码中设置服务器URL,以便它知道向哪里发送网络请求。然后,dioxus_desktop启动应用程序。

对于开发,示例项目在localhost:8080上运行服务器。在发布之前记得更新URL到你的生产URL。

服务器代码

在服务器代码中,首先你需要设置服务器将监听的网络地址和端口。

let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
    .await
    .unwrap();
println!("listening on http://127.0.0.1:3000");

然后,你必须在服务器中注册在服务器函数宏中声明的类型,这将向服务器添加相应的路由。

register_explicit::<GetServerData>();

最后,启动服务器并开始响应请求。

img