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

后记

分享是一种态度

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

相关文章
|
28天前
|
存储 JavaScript 前端开发
掌握现代Web开发的基石:深入理解React与Redux
【10月更文挑战第14天】掌握现代Web开发的基石:深入理解React与Redux
31 0
|
1月前
|
前端开发 JavaScript UED
构建现代Web应用:使用React框架打造单页面应用
【10月更文挑战第9天】构建现代Web应用:使用React框架打造单页面应用
|
1月前
|
前端开发 JavaScript 测试技术
构建响应式Web应用程序:React实战指南
【10月更文挑战第9天】构建响应式Web应用程序:React实战指南
|
1月前
|
前端开发 JavaScript 开发者
探索现代Web前端技术:React框架入门
【10月更文挑战第9天】 探索现代Web前端技术:React框架入门
|
1月前
|
存储 JavaScript 前端开发
如何使用React和Redux构建现代化Web应用程序
【10月更文挑战第4天】如何使用React和Redux构建现代化Web应用程序
|
1月前
|
Java PHP
PHP作为广受青睐的服务器端脚本语言,在Web开发中占据重要地位。理解其垃圾回收机制有助于开发高效稳定的PHP应用。
【10月更文挑战第1天】PHP作为广受青睐的服务器端脚本语言,在Web开发中占据重要地位。其垃圾回收机制包括引用计数与循环垃圾回收,对提升应用性能和稳定性至关重要。本文通过具体案例分析,详细探讨PHP垃圾回收机制的工作原理,特别是如何解决循环引用问题。在PHP 8中,垃圾回收机制得到进一步优化,提高了效率和准确性。理解这些机制有助于开发高效稳定的PHP应用。
43 3
|
3月前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
157 0
|
3月前
|
Rust 安全 开发者
惊爆!Xamarin 携手机器学习,开启智能应用新纪元,个性化体验与跨平台优势完美融合大揭秘!
【8月更文挑战第31天】随着互联网的发展,Web应用对性能和安全性要求不断提高。Rust凭借卓越的性能、内存安全及丰富生态,成为构建高性能Web服务器的理想选择。本文通过一个简单示例,展示如何使用Rust和Actix-web框架搭建基本Web服务器,从创建项目到运行服务器全程指导,帮助读者领略Rust在Web后端开发中的强大能力。通过实践,读者可以体验到Rust在性能和安全性方面的优势,以及其在Web开发领域的巨大潜力。
39 0
|
3月前
|
Java 数据库 API
JSF与JPA的史诗级联盟:如何编织数据持久化的华丽织锦,重塑Web应用的荣耀
【8月更文挑战第31天】JavaServer Faces (JSF) 和 Java Persistence API (JPA) 分别是构建Java Web应用的用户界面组件框架和持久化标准。结合使用JSF与JPA,能够打造强大的数据驱动Web应用。首先,通过定义实体类(如`User`)和配置`persistence.xml`来设置JPA环境。然后,在JSF中利用Managed Bean(如`UserBean`)管理业务逻辑,通过`EntityManager`执行数据持久化操作。
55 0
|
10天前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
36 9