用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. ....

后记

分享是一种态度

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

相关文章
|
2月前
|
弹性计算 运维 监控
slb后端服务器组(Backend Server Group)关联
阿里云SLB的后端服务器组提供逻辑分组、精细化路由、端口隔离、权重分配与会话保持等功能,便于资源管理、流量控制和故障隔离。通过监听规则、健康检查、标签和API关联,实现灵活的后端资源配置,适应多变的业务需求,确保服务稳定性和资源利用率。
21 1
|
2月前
|
存储 数据安全/隐私保护 索引
Windows Server 各版本搭建文件服务器实现共享文件(03~19)
Windows Server 各版本搭建文件服务器实现共享文件(03~19)
|
4天前
|
网络协议 安全
ensp中nat server 公网访问内网服务器
ensp中nat server 公网访问内网服务器
|
4天前
|
开发框架 JavaScript 安全
WIndows Server 2016 部署 Web服务(简单篇)
WIndows Server 2016 部署 Web服务(简单篇)
|
11天前
|
网络协议 Windows
Windows Server 各版本搭建 DNS 服务器实现域名正反向解析
Windows Server 各版本搭建 DNS 服务器实现域名正反向解析
|
26天前
|
前端开发 JavaScript vr&ar
前端新技术探索:WebAssembly、Web Components与WebVR/AR
【4月更文挑战第12天】WebAssembly、Web Components和WebVR/AR正重塑Web应用的未来。WebAssembly允许C/C++等语言在Web上高效运行,提供接近原生的性能,如游戏引擎。Web Components通过Custom Elements和Shadow DOM实现可复用的自定义UI组件,提升模块化开发。WebVR/AR(现WebXR)则让VR/AR体验无需额外应用,直接在浏览器中实现。掌握这些技术对前端开发者至关重要。
16 3
|
2月前
|
运维 JavaScript 前端开发
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
|
2月前
|
存储 Windows
windows server 2019 云服务器看不见硬盘的解决方案
windows server 2019 云服务器看不见硬盘的解决方案
|
2月前
|
数据安全/隐私保护 Windows
Windows Server 各版本搭建终端服务器实现远程访问(03~19)
左下角开始➡管理工具➡管理您的服务器,点击添加或删除角色点击下一步勾选自定义,点击下一步蒂埃涅吉终端服务器,点击下一步点击确定重新登录后点击确定点击开始➡管理工具➡计算机管理,展开本地用户和组,点击组可以发现有个组关门用来远程登录右键这个组点击属性,点击添加输入要添加的用户名,点击确定添加成功后点击确定打开另一台虚拟机(前提是在同一个局域网内),按 WIN + R 输入 mstsc 后回车输入 IP 地址后点击连接输入用户名及密码后点击确定连接成功!