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


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

    #[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>
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    代码解析

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

    let data_state = {
            let datas = datas.clone();
            use_state(move || datas)
        };
    
    • 1
    • 2
    • 3
    • 4

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

        let data = (*data_state).clone().into_iter().collect::<Vec<TableRow>>();
        ...
        Ok(html! {
            <div class="column">
                ...
                <TableComponent data={data} on_delete={on_delete_row}/>
                ...
            </div>
        })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

        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");
                    }
                });
            }
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

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

        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),
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    总结

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

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

  • 相关阅读:
    Java资深架构师详解java进阶技术体系与主流架构思维(建议入手)
    学系统集成项目管理工程师(中项)系列28_后记
    科技成果鉴定测试报告一般包含哪些测试内容?
    HTML 实时显示本地电脑时间(精确到毫秒)
    怎么压缩视频?视频过大跟我这样压缩
    牛客网《剑指offer》专栏刷题练习之数组专精
    第二章 进程与线程 十五、互斥锁
    毕业设计-电子商务网站(一)
    六款 Linux 常用远程连接工具介绍
    KunlunBase MeetUP 等您来!
  • 原文地址:https://blog.csdn.net/firefox1/article/details/133364549