用Rust搭建React Server Components 的Web服务器(三)

简介: 用Rust搭建React Server Components 的Web服务器(三)

3. Server components

使用了RSC,服务器最终将JSX组件呈现为HTML字符串,就像我们前面所做的那样。

然后,我们上面的代码示例中,大部分都是基于fromat!()或者它的改进版本Maud对字符串进行页面结构的拼装。上面的写法显然不够优雅。

对于熟悉了JSX的语法的前端开发者,我们还是希望能够有一种类JSX的语法方式,让我们能够产生比较小的心里负担来开发页面。

那么,我们可以使用rscx来构建我们的页面结构。(具体的使用情况可以参考前置知识点

首先我们在Cargo.toml中引入对应的项目

[dependencies]
rscx = { version = "0.1.8", features = ["axum"] }

format!()相比,JSX提供了显著更好的开发体验。

让我们使用rscx来改造上面的例子。

改造Layout 和 Count

#[props]
struct LayoutProps {
    #[builder(default)]
    children: String,
}
#[component]
async fn Layout(props: LayoutProps) -> String {
    let s = "h1 { color: red; }";
    html! {
       <!DOCTYPE html>
        <html>
            <head>
                <style>{s}</style>
            </head>
            <body>
                <h1>柒八九陪你一起学Rust</h1>
                {props.children}
            </body>
        </html>
    }
}
#[props]
struct CountProps {
    #[builder(default = 0)]
    count: i32,
}
#[component]
fn Count(props: CountProps) -> String {
    let count = props.count;
    html! {
        <p>
            {
                if count < 0 {
                    "数字为负数".to_string()
                } else {
                    format!("{}!", count)
                }
            }
        </p>
    }
}

改造PageN

async fn page1() -> impl IntoResponse {
    render_with_meta(|| async {
        html! {
            <Layout>
                <Count count=-1/>
            </Layout>
        }
    })
    .await
}
async fn page2() -> impl IntoResponse {
    render_with_meta(|| async {
        html! {
            <Layout>
                <Count count=789/>
            </Layout>
        }
    })
    .await
}

工具函数 render_with_meta

async fn render_with_meta<F>(
    render_fn: impl FnOnce() -> F + Send + 'static,
) -> axum::response::Html<String>
where
    F: futures::Future<Output = String> + Send + 'static,
{
    rscx::axum::render(async move { render_fn().await }).await
}

其中,render_with_meta我们需要额外的关注一下:

这段代码定义了一个名为 render_with_meta 的异步函数,该函数接受一个闭包 render_fn 作为参数。这个函数的主要目的是将一个异步渲染函数包装起来,以便在 Axum 框架中进行处理,并返回一个 HTML 响应。

以下是对这段代码的详细解释:

  1. async fn render_with_meta<F>(...) -> axum::response::Html<String>
  • 这是一个异步函数,它返回一个 HTML 响应,响应内容是一个 String
  • 函数接受一个名为 render_fn 的参数,该参数是一个闭包,闭包的返回值是一个实现了 Future trait 的类型(F)。
  1. where F: futures::Future<Output = String> + Send + 'static
  • 这是一个泛型约束,限定了闭包 render_fn 返回的类型 F 必须是一个实现了 Future trait 的类型,并且其 Output 类型是 String
  • Send 表示 F 必须是可跨线程发送的。
  • 'static 表示 F 必须具有静态生命周期,这意味着它可以在整个程序运行期间保持有效。
  1. 函数体:
  • 函数体开始时调用了 rscx::axum::render 函数,该函数似乎是用于渲染的工具函数,接受一个异步闭包作为参数。
  • 在这个异步闭包中,我们使用 async move { render_fn().await } 来调用传入的 render_fn,并等待它的结果。这部分代码负责实际的渲染工作。
  1. rscx::axum::render 返回的结果再次被 await,这表示整个异步函数 render_with_meta 将等待渲染完成,然后返回一个 HTML 响应对象,响应内容是渲染结果的 String

这个函数的主要目的是将渲染逻辑封装在一个异步函数中,并处理异步渲染的细节,最终返回一个 HTML 响应。它可以帮助你在 Axum 框架中更方便地处理异步渲染任务。在调用该函数时,你需要传递一个异步闭包,该闭包负责实际的渲染工作,并返回一个 Future,其 Output 类型是 String。函数内部会处理异步操作,确保返回一个完整的 HTML 响应对象。

image.png


4. 页面响应交互事件

创建一些静态或近似静态内容是很简单的。

但是,一个静态的网页对于我们开发来说,就像下孩子过家家一样。


下面,我们将在页面中新增一个button,用于记录按钮被点击的次数。

use maud::{html, Markup};
static mut COUNTER: u32 = 0;
fn counter() -> Markup {
    // 从一个真实的数据库中获取数据,而不是访问全局状态
    let c = unsafe { COUNTER };
    html! {
        div {
            p { "总数为:" (c) }
            button { "数字加1" }
        }
    }
}
async fn page1() -> impl IntoResponse {
    html! {
        h1 { "页面可交互" }
        (counter())
    }
}

image.png

点击按钮目前还没有任何反应。

我们将使用htmx JavaScript库。通过编写适量的JavaScript代码,就可以响应一下事件回掉。

当用户点击按钮时,它将发送一个POST /components/counter/increment请求到我们的服务器,服务器会在其全局状态中更新计数器并返回更新后计数器的修改后的HTML。

让我们在Axum的路由器中注册一个新的counter_increment()路由处理程序。对于响应,我们可以重用之前定义的counter()函数。

// 向axum路由器注册特定于此组件的新路由
fn register(router: Router) -> Router {
    router.route("/components/counter/increment", post(counter_increment))
}
static mut COUNTER: u32 = 0;
async fn counter_increment() -> Markup {
    // 更新状态
    unsafe { COUNTER += 1 };
    // 返回更新后的HTML
    counter()
}
fn counter() -> Markup {
    // 从一个真实的数据库中获取数据,而不是访问全局状态
    let c = unsafe { COUNTER };
    html! {
        div {
            p { "总数为:" (c) }
            button { "数字加1" }
        }
    }
}
async fn page1() -> impl IntoResponse {
    html! {
        h1 { "页面可交互" }
        (counter())
    }
}

这里有一点需要注意:我们需要将之前定义的Router对象传人。所以,在main中,我们也需要做一次改造。

async fn main() {
    let app = Router::new().route("/page1", get(page1));
    let app = register(app);
    // 省略下面代码。
}

这样,当用户点击按钮时,服务器将处理请求并更新计数器,然后返回更新后的计数器HTML,从而实现交互性。

我们可以使用curl轻松测试我们的新端点:

$ curl -XPOST http://localhost:3000/components/counter/increment
<div><p>总数为: 1</p><button>数字加1</button></div>
$ curl -XPOST http://localhost:3000/components/counter/increment
<div><p>总数为: 2</p><button>数字加1</button></div>

到这步了,说明我们的应用可以根据对应的请求,返回指定的HTML结构了。

为了使其具有交互性,让我们添加一个点击事件,该事件发送相同的HTTP请求并用响应替换其内容:

fn counter() -> Markup {
    let c = unsafe { COUNTER };
    html! {
        div {
            p { "总数为:" (c) }
            button
                // 目标元素将被替换
                hx-target="closest div"
                // 请求的方法和URL
                hx-post="/components/counter/increment"
                // 由于它是一个按钮,默认情况下,htmx会将触发器设置为点击事件
                { "数字加1" }
        }
    }
}
async fn page1() -> impl IntoResponse {
    html! {
         // 从CDN添加htmx
        script src="https://unpkg.com/htmx.org@1.9.3" {}
        h1 { "页面可交互" }
        (counter())
    }
}

就是这样,它就可以工作了:

image.png

上面的hx-XXhtmx的语法,在这里我们就不展开说明。感兴趣的同学,可以前往htmx官网学习。

上面的代码使用maud语法构建的组件,如果有兴趣,可以换成rscx。效果是等同的。


5. 新增 <Suspense />特性

上面的例子,虽然代码很少,但是也算的上是一个功能完备的RSC了。我们还想更进一步,针对Suspense特性,让我们的页面有更好的用户交互体验。

React中的Suspense组件真正的用途是:在需要渲染想要的展示的组件时候,在服务器上仍渲染时一个回退组件。

考虑我们之前的counter()组件;想象一下我们需要从一个需要500毫秒的第三方服务中检索该数字。在关键内容渲染之后,我们应该让客户端延迟获取counter()组件。

image.png

我们继续使用htmx,事件绑定问题。首先,我将创建一个新的GET /components/counter路由,只返回counter组件:

fn register(router: Router) -> Router {
    router
        .route("/components/counter", get(counter_get))
        .route("/components/counter/increment", post(counter_increment))
}
async fn counter_get() -> Markup {
    counter()
}

而且,因为我们不再想渲染counter(),所以让我们用一个占位符div来代替,这个div将在页面准备好后触发GET请求:

async fn page1() -> impl IntoResponse {
    html! {
        script src="https://unpkg.com/htmx.org@1.9.3" {}
        h1 { "Suspence" }
        // 这个div将在页面加载后立即被替换
        div hx-trigger="load" hx-get="/components/counter" {
            p { "总数为: 正在加载中...." }
        }
    }
}

我们也可以编写自己的suspense()组件来保持整洁!

fn suspense(route: &str, placeholder: Markup) -> Markup {
    html! {
        // 这个div将在页面加载后立即被替换
        div hx-trigger="load" hx-get=(route) {
            (placeholder)
        }
    }
}

这样我们可以在我们想要的地方调用它

async fn page1() -> impl IntoResponse {
    html! {
         // 从CDN添加htmx
        script src="https://unpkg.com/htmx.org@1.9.3" {}
        h1 { "页面可交互" }
        // 这个div将在页面加载后立即被替换
        { (suspense("/components/counter",html!{"总数为: 正在加载中...."}))}
    }
}

在页面加载过程中,我们会看到页面中有一瞬间显示的是,Suspence的内容

image.png


待做的部分

上面的内容,我们利用axummaud或者rscxhtmx构建了一个功能完备的RSC服务,其实还有很多东西没完善。

比方说:

  1. 组件库 - dioxus
  2. 引入样式 - tailwindcss
  3. 状态管理
  4. ....

后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。

相关文章
|
4天前
计算机网络:思科实验【1-访问WEB服务器】
计算机网络:思科实验【1-访问WEB服务器】
计算机网络:思科实验【1-访问WEB服务器】
|
9天前
|
数据可视化 Shell Linux
shell+crontab+gitlab实现ecs服务器文件的web展示
本文通过把ecs服务器上的文件定时上传至gitlab,实现文件的页面可视化和修改历史。技术点:shell、crontab、gitlab。
37 3
|
1月前
|
负载均衡 应用服务中间件 API
什么是 Web 服务器领域的 openresty
什么是 Web 服务器领域的 openresty
31 0
|
2月前
|
IDE Linux 开发工具
如何在Linux运行RStudio Server并实现Web浏览器远程访问
如何在Linux运行RStudio Server并实现Web浏览器远程访问
42 0
|
2月前
|
存储 运维 应用服务中间件
[运维日志] Web 服务器日志依日期归档(Powershell 实现,附源代码)
[运维日志] Web 服务器日志依日期归档(Powershell 实现,附源代码)
57 0
|
28天前
|
应用服务中间件 nginx
【报错】Failed to start A high performance web server and a reverse proxy server.
【报错】Failed to start A high performance web server and a reverse proxy server.
|
3天前
|
安全 网络安全 开发者
如何在OpenWRT部署uhttpd搭建服务器实现远程访问本地web站点
如何在OpenWRT部署uhttpd搭建服务器实现远程访问本地web站点
16 0
|
4天前
|
存储 缓存 网络协议
Go语言并发编程实战:构建高性能Web服务器
【2月更文挑战第6天】本文将通过构建一个高性能的Web服务器实战案例,深入探讨如何在Go语言中运用并发编程技术。我们将利用goroutine和channel实现高效的请求处理、资源管理和并发控制,以提升Web服务器的性能和稳定性。通过这一实战,你将更好地理解和掌握Go语言在并发编程方面的优势和应用。
|
10天前
|
负载均衡 JavaScript 应用服务中间件
强大的WEB服务器-Nginx
强大的WEB服务器-Nginx
18 0
|
25天前
|
Kubernetes 应用服务中间件 nginx
K8S Pod Sidecar 应用场景之一 - 加入 NGINX Sidecar 做反代和 web 服务器
K8S Pod Sidecar 应用场景之一 - 加入 NGINX Sidecar 做反代和 web 服务器

相关产品

  • 云迁移中心