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

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

2. 服务端渲染

说一个事实,其实见到的网页都是通过网络传输的HTML构建而成(毕竟HTML构成了网页的骨架)。 只不过,有些网页是一股脑的所有HTML都返回了,而有的网页是通过SPA/SSR等资源分离技术通过Node/JS/React等各种眼花缭乱的技术来构建一个网站。

构建服务器

让我们使用Axum作为应用框架构建一个最简单的Web服务器

image.png

首先,我们先在Cargo.toml中引入axumtokio。这两具体干啥的,我们在前面介绍了,这里就不过赘述了。

[dependencies]
axum = { version = "0.6.20" }
tokio = { version = "1", features = ["full"] }

main.rs

下面这段代码是构建一个监听指定端口的服务器。并对指定路径的做出反应。

use axum::{  
  response::Html, 
  response::IntoResponse, 
  routing::get, Router
  };
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
   // 创建一个 Axum 应用程序
   let app = Router::new()
       .route("/page1", get(page1))
       .route("/page2", get(page2));
   // 指定服务器地址(这里监听本地的 127.0.0.1:3000)
   let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
   // 使用 Axum 的服务器绑定和服务方法启动服务器
   axum::Server::bind(&addr)
       .serve(app.into_make_service())
       .await
       .unwrap();
}
async fn page1() -> impl IntoResponse {
    Html("<h1>Page 1</h1>")
}
async fn page2() -> impl IntoResponse {
    Html("<h1>Page 2!!!</h1>")
}

我们对其中核心代码做简单的分析和解释:

  1. main函数:
  • #[tokio::main] 注解标识 main 函数是异步的,这意味着它可以在一个异步运行时环境中执行。
  • main 函数中,首先通过 Router::new() 创建了一个 Axum 应用程序 app,然后使用 .route() 方法定义了两个路由规则:一个是 /page1,另一个是 /page2,分别映射到 page1page2 函数。
  • 接着,通过 SocketAddr 创建了服务器地址对象,指定服务器监听的地址和端口。
  • 最后,使用 AxumServer::bind() 方法绑定服务器地址,并使用 .serve() 方法启动服务器。服务器将处理传入的请求,并根据路由规则调用相应的处理函数。
  1. page1page2函数:
  • 这两个函数是路由处理函数,它们接收请求并返回响应。
  • 这里的 page1 函数返回一个 Html 响应,其中包含一个简单的标题标签 <h1>Page 1</h1>
  • 同样,page2 函数返回一个 Html 响应,其中包含 <h1>Page 2!!!</h1>

使用cargo run后,我们可以在浏览器中,通过访问对应的页面地址进行页面展示。

image.png



页面共有逻辑抽离

在我们页面开发中,总是会有多个页面拥有共同的布局和样式,我们可以对其进行抽离。也就是消除重复的HTML,此时我们可以使用format!()来操作。

// 代码省略
async fn layout(content: &'static str) -> String {
    format!(
        "
        <h1>柒八九陪你一起学Rust</h1>
        {content}
        "
    )
}
async fn page1() -> impl IntoResponse {
    Html(layout("<h1>Page 1</h1>").await)
}
async fn page2() -> impl IntoResponse {
    Html(layout("<h1>Page 2!!!</h1>").await)
}

上面代码中最重要的是 content 参数的类型是 &'static str,这意味着它是一个静态字符串切片,其生命周期被标记为 'static,表示这个字符串切片在整个程序运行期间都有效,不会被销毁。这样做是为了确保在异步函数中使用字符串切片时,不会出现生命周期问题,因为异步函数可能会在较长的时间内挂起。

同时函数签名定义为async fn 表示这是一个异步函数,它可以在执行期间挂起而不会阻塞整个线程。

我们还是熟悉的配方,在浏览器中访问对应的页面地址。

image.png


在Rust中定义组件

熟悉前端开发的同学,感觉到这种逻辑或者页面结构抽离很熟悉,这不就是我们经常挂在嘴边的组件封装吗。既然页面结构可以进行抽离,那如果我们站在页面全链路角度来看,那是不是我们可以在服务器中定义我们页面中需要展示的组件,然后再输出到浏览器中。

咦,这个观点和概念是不是又感觉似曾相识。这就是我们之前讲过的RSC

例如,现在有一个React组件。

// 一个JSX组件
function Count({ count }) {
    if (count < 0) {
        return <p>数字为负数</p>;
    }
    return <h1>{count}</h1>;
}

等价于Rust服务器组件

fn count(count: i64) -> String {
    if count < 0 {
        "<p>数字为负数</p>".into()
    } else {
        format!("<h1>{count}</h1>")
    }
}

可能大家没注意到,这里做一下拓展,在React中我们定义组件的时候,我们有一个不成文的规定,组件首字母大写,例如上面的例子中Count。 而到了Rust中定义组件时候,组件名称变成了小写了(count)。其实这也是Rust的不成文规定。这是因为Rust代码使用{蛇形命名法|Snake Case} 来作为规范函数和变量名称的风格。而蛇形命名法只使用小写的字母进行命名,并以下画线分隔单词

use axum::{
  response::Html, 
  response::IntoResponse, 
  routing::get, Router
};
// 省略部分代码
async fn layout(content: String) -> String {
    format!(
        "
        <h1>柒八九陪你一起学Rust</h1>
        {content}
        "
    )
}
fn count(count: i64) -> String {
    if count < 0 {
        "<p>数字为负数</p>".into()
    } else {
        format!("<h1>{count}</h1>")
    }
}
async fn page1() -> impl IntoResponse {
    Html(layout(count(789)).await)
}
async fn page2() -> impl IntoResponse {
    Html(layout(count(-1)).await)
}

我们可以将count引入到我们的页面中。

image.png


注意:上面例子中,layout(content: String)的函数签名变成接受String了。(不过这里不是很重要)


优化组件定义方式

我们上面的代码示例中,是利用fromat!()对字符串进行页面结构的拼装,在一些简单的页面这种方式还是很有作用的,但是如果阁下遇到页面逻辑复杂,层级众多的时候,使用format!()就有点捉襟见肘了。

我们可以使用Maud crate,来重新处理上面的组件。

image.png

下面,我们做一次简单的改造。

首先,我们引入了一个新的依赖项。

[dependencies]
maud = { varsion = "0.25.0", features = ["axum"] } 

这里的features是适配各种Rust框架的,具体情况可以参考Web framework integration

使用MaudRSC

fn count(count: i64) -> Markup {
    // 我们仍然可以在这里放一些其他逻辑。
    // 但是,Maud模板方便地支持if、let、match和循环。
    html! {
        @if (count < 0) {
            p { "数字为负数" }
        } else {
            h1 { (count) }
        }
    }
}

我们现在将format!()maud替换。代码如下:

// ...省略
use maud::{html, Markup};
// 省略main 函数
async fn layout(content: Markup) -> Markup {
    html! {
        h1 {"柒八九陪你一起学Rust"}
        {(content)}
    }
}
fn count(count: i64) -> Markup {
    html! {
        @if count < 0 {
            p {"数字为负数"}
        }@ else {
            h1 {(count)}
        }
    }
}
async fn page1() -> impl IntoResponse {
    html! {
        {(layout(count(789)).await)}
    }
}
async fn page2() -> impl IntoResponse {
    html! {
        {(layout(count(-1)).await)}
    }
}

然后,我们继续访问对应的网址,可以查看一下效果。(亲测有效,这里就不贴图了)

细心的小伙伴,可能使用Maud时,函数签名中使用了Markup而不是String。这里简单的解释一下。

Markup是一个字符串,但它也是一种表示包含HTML的字符串的方式。默认情况下,Maud会转义字符串内容。直接返回Markup更容易嵌套Maud组件

Last but not least,最浓墨重彩的一笔是,MaudRender特性

默认情况下,Maud会使用标准的Display特性将组件呈现为HTML。类型可以通过实现Render来自定义其输出。

这对于创建自己的组件非常有用:

我们可以在各自页面或者共有页面中引入对应的样式信息。对应的代码如下。

struct Css(&'static str);
impl Render for Css {
    fn render(&self) -> Markup {
        html! {
            link rel="stylesheet" type="text/css" href=(self.0);
        }
    }
}
async fn layout(content: Markup) -> Markup {
    // 创建一个 Stylesheet 实例,传入样式表的链接地址
    let stylesheet = Stylesheet("./styles.css");
    // 使用 render 方法将 Stylesheet 实例转换为 Markup
    let stylesheet_markup: Markup = stylesheet.render();
    html! {
        head {
            title {"Rust 搭建RSC服务器"}
            // 插入样式表链接
            (stylesheet_markup)
        }
        body {
            h1 {"柒八九陪你一起学Rust"}
            {(content)}
        }
    }
}

image.png

相关文章
|
19天前
|
存储 JavaScript 前端开发
掌握现代Web开发的基石:深入理解React与Redux
【10月更文挑战第14天】掌握现代Web开发的基石:深入理解React与Redux
28 0
|
24天前
|
前端开发 JavaScript UED
构建现代Web应用:使用React框架打造单页面应用
【10月更文挑战第9天】构建现代Web应用:使用React框架打造单页面应用
|
24天前
|
前端开发 JavaScript 测试技术
构建响应式Web应用程序:React实战指南
【10月更文挑战第9天】构建响应式Web应用程序:React实战指南
|
24天前
|
前端开发 JavaScript 开发者
探索现代Web前端技术:React框架入门
【10月更文挑战第9天】 探索现代Web前端技术:React框架入门
|
30天前
|
存储 JavaScript 前端开发
如何使用React和Redux构建现代化Web应用程序
【10月更文挑战第4天】如何使用React和Redux构建现代化Web应用程序
|
1月前
|
Java PHP
PHP作为广受青睐的服务器端脚本语言,在Web开发中占据重要地位。理解其垃圾回收机制有助于开发高效稳定的PHP应用。
【10月更文挑战第1天】PHP作为广受青睐的服务器端脚本语言,在Web开发中占据重要地位。其垃圾回收机制包括引用计数与循环垃圾回收,对提升应用性能和稳定性至关重要。本文通过具体案例分析,详细探讨PHP垃圾回收机制的工作原理,特别是如何解决循环引用问题。在PHP 8中,垃圾回收机制得到进一步优化,提高了效率和准确性。理解这些机制有助于开发高效稳定的PHP应用。
41 3
|
2月前
|
JSON Rust 安全
30天拿下Rust之实战Web Server
30天拿下Rust之实战Web Server
50 7
|
3月前
|
JavaScript 搜索推荐 前端开发
从零搭建到部署:Angular与Angular Universal手把手教你实现服务器端渲染(SSR),全面解析及实战指南助你提升Web应用性能与SEO优化效果
【8月更文挑战第31天】服务器端渲染(SSR)是现代Web开发的关键技术,能显著提升SEO效果及首屏加载速度,改善用户体验。Angular Universal作为官方SSR解决方案,允许在服务器端生成静态HTML文件。本文通过具体示例详细介绍如何使用Angular Universal实现SSR,并分享最佳实践。首先需安装Node.js和npm。
61 1
|
3月前
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
67 1
|
3月前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
141 0