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

后记

分享是一种态度

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

相关文章
|
9天前
|
中间件 Java 应用服务中间件
Windows部署web应用服务器Jboss中间件
如何在Windows系统上部署JBoss 7.1作为Web应用服务器,包括配置环境变量、自动部署WAR包、访问JBoss控制台、设置管理员账户以及修改端口和绑定地址等操作。
25 1
|
3天前
|
缓存 NoSQL 数据库
高性能Web服务器架构设计
【8月更文第28天】在当今互联网时代,网站的响应速度直接影响用户体验和业务成功率。因此,构建一个高性能的Web服务器架构至关重要。本文将从硬件配置、软件架构以及网络设置三个方面探讨如何提高Web服务器的性能,并提供一些实际的代码示例。
16 0
|
16天前
|
开发框架 缓存 .NET
并发请求太多,服务器崩溃了?试试使用 ASP.NET Core Web API 操作筛选器对请求进行限流
并发请求太多,服务器崩溃了?试试使用 ASP.NET Core Web API 操作筛选器对请求进行限流
|
13天前
|
前端开发 JavaScript 算法
React Server Component 使用问题之想在路由切换时保持客户端状态,如何实现
React Server Component 使用问题之想在路由切换时保持客户端状态,如何实现
|
13天前
|
前端开发 JavaScript PHP
React Server Component 使用问题之路由的能力,如何实现
React Server Component 使用问题之路由的能力,如何实现
|
13天前
|
前端开发 JavaScript
React Server Component 使用问题之添加jsx的组件化能力,如何操作
React Server Component 使用问题之添加jsx的组件化能力,如何操作
|
13天前
|
前端开发 PHP 开发者
React Server Component 使用问题之怎么使用Docker运行PHP应用
React Server Component 使用问题之怎么使用Docker运行PHP应用
|
15天前
|
JSON 前端开发 JavaScript
Web中的客户端和服务器端
Web中的客户端和服务器端
|
1天前
|
JavaScript 前端开发 UED
服务器端渲染新浪潮:用Vue.js和Nuxt.js构建高性能Web应用
【8月更文挑战第30天】在现代Web开发中,提升应用性能和SEO友好性是前端开发者面临的挑战。服务器端渲染(SSR)能加快页面加载速度并改善搜索引擎优化。Vue.js结合Nuxt.js提供了一个高效框架来创建SSR应用。通过安装`create-nuxt-app`,可以轻松创建新的Nuxt.js项目,并利用其自动路由功能简化页面管理。Nuxt.js默认采用SSR模式,并支持通过`asyncData`方法预取数据,同时提供了静态站点生成和服务器端渲染的部署选项,显著提升用户体验。
|
2天前
|
数据可视化 Python
通过python建立一个web服务查看服务器上的文本、图片、视频等文件
通过python建立一个web服务查看服务器上的文本、图片、视频等文件
6 0
下一篇
云函数