与服务器通信
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_future与use_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>();
最后,启动服务器并开始响应请求。