[2023.09.27]: Yew SSR开发中的服务器端与客户端共同维护同一状态的实践

SSR(Server-Side Rendering)具有许多优势,其中之一就是能够在服务器端生成页面,从而提高整个页面的加载速度。在Yew SSR开发模式中,我们可以使用use_prepared_state宏在服务器端获取数据并生成Html返回到客户端。官方提供了相应的例子供参考。然而,如果在客户端修改并提交了该数据,我们需要如何刷新之前在服务器端生成的数据呢?官方未提供相应例子,经过多次实验后,以下代码可以实现该功能。

rust 复制代码
#[function_component]
fn Content() -> HtmlResult {
    let datas: Vec<Note> =
        use_prepared_state!(async move |_| -> Vec<Note> { fetch_notes().await }, ())?
            .unwrap()
            .to_vec();
    let data_state = {
        let datas = datas.clone();
        use_state(move || datas)
    };
    let data = (*data_state).clone().into_iter().collect::<Vec<TableRow>>();

    ...
    
    let reload = use_callback(
        move |_, data_state1| {
            let data_state1 = data_state1.clone();
            spawn_local(async move {
                if let Ok(resp) = Request::get("/api/notes").send().await {
                    if let Ok(datas1) = resp.json::<Vec<Note>>().await {
                        data_state1.set(datas1);
                    }
                }
            });
        },
        (data_state),
    );
    
    let on_delete_row = {
        let reload = reload.clone();
        move |row: TableRow| {
            let reload = reload.clone();
            spawn_local(async move {
                if let Ok(req) = Request::delete(&format!("/api/notes/{}", row.id)).build() {
                    let _ = req.send().await;
                    reload.emit(());
                } else {
                    log!("delete failed");
                }
            });
        }
    };

    
    Ok(html! {
        <div class="column">
            ...
            <TableComponent data={data} on_delete={on_delete_row}/>
            ...
        </div>
    })
}

代码解析

上面的代码是一个Yew的函数式组件。在Yew SSR开发模式中,官方推荐使用函数式组件来进行开发。
datas是在服务器端生成的数据。紧接着我们用use_state来引用这个数据。因此,后面的处理就围绕data_state来进行。

rust 复制代码
let data_state = {
        let datas = datas.clone();
        use_state(move || datas)
    };

data_state中获取数据,并将其直接传入组件的属性,从而使数据渲染到界面上。

rust 复制代码
    let data = (*data_state).clone().into_iter().collect::<Vec<TableRow>>();
    ...
    Ok(html! {
        <div class="column">
            ...
            <TableComponent data={data} on_delete={on_delete_row}/>
            ...
        </div>
    })

当数据通过on_delete_row被删除后,需要重新获取数据,然后刷新该数据在界面上的显示,注意这个过程是在客户端发生的。当然,我们可以强行刷新页面,利用服务器端来从新生成数据,即datas。但是这种方式体验感不好,感觉回到了20年前的Asp.net的页面开发。

rust 复制代码
    let on_delete_row = {
        let reload = reload.clone();
        move |row: TableRow| {
            let reload = reload.clone();
            spawn_local(async move {
                if let Ok(req) = Request::delete(&format!("/api/notes/{}", row.id)).build() {
                    let _ = req.send().await;
                    reload.emit(());
                } else {
                    log!("delete failed");
                }
            });
        }
    };

在上面的代码中reload的触发就是为了重新加载并刷新页面。在这里使用了Future,因此引入了asyncawait。玩法和Javascript中的asyncawait类似,只能在async代码块中使用awaitwasm_bindgen_futures::spawn_local函数是一种在WebAssembly中启动异步任务的方式,因此访问后端api的地方都会用到spawn_local函数。

最后,在reload回调函数中,通过data_state1.set(datas1),将后端获取到的数据更新到界面上。

rust 复制代码
    let reload = use_callback(
        move |_, data_state1| {
            let data_state1 = data_state1.clone();
            spawn_local(async move {
                if let Ok(resp) = Request::get("/api/notes").send().await {
                    if let Ok(datas1) = resp.json::<Vec<Note>>().await {
                        data_state1.set(datas1);
                    }
                }
            });
        },
        (data_state),
    );

总结

通过use_state引用服务器端生成的数据,可以实现服务器端和客户端对同一段数据的刷新。在服务器端生成数据是为了提升页面的加载速度;在客户端获取数据是为了灵活的处理手段。这两者的结合,无疑让我们的应用会有更好的表现。

今天就写到这里,欢迎大家留言交流。

相关推荐
全栈前端老曹3 分钟前
【MongoDB】安全与权限管理 —— 用户认证、角色权限、SSL 加密
前端·javascript·数据库·安全·mongodb·nosql·ssl
zhangxingchao1 小时前
AI大模型核心八:从 Agent Skill、长文档 RAG 到知识库更新与训练策略
前端·人工智能·后端
zhangxingchao1 小时前
AI大模型核心七:从 Workflow、RAG、记忆治理到幂等性
前端·人工智能·后端
耍酷的魔镜2 小时前
谈谈如何使用Netty开发实现高性能的RPC服务器
服务器·网络协议·rpc
gyx_这个杀手不太冷静2 小时前
Agent开发进阶指南(第 1 章):AI Agent 到底是什么?
前端·agent·ai编程
小小小小宇3 小时前
模型相关知识
前端
小小小小宇3 小时前
模型相关知识二
前端
IT_陈寒3 小时前
Python装饰器竟然偷偷改了函数名?这个坑我爬了三天
前端·人工智能·后端
程序员在囧途3 小时前
likeadmin-api API 中转站怎么做统一报价?从 /pricing、/user/balance 到 task_id 回执的落地方法
运维·服务器·数据库·likeadmin-api·api中转站·token计费
cookies_s_s4 小时前
C++ 字符串动态创建对象 -- 工厂模式、自动注册、模板递归动态调用
服务器·开发语言·c++