[2023.09.20]:Yew的前端开发经历小结

news2024/9/27 19:26:09

今天基本上完成了一个操作闭环,即能够保存,拉取和删除数据。截个图
在这里插入图片描述
这个过程的前端和后端都是用Rust写的,前端使用的是Yew。

Yew是一种用于构建现代Web应用程序的Rust框架,其计目标是提供一种安全、高效、易用的方式来构建Web应用程序。Yew基于WebAssembly(Wasm)技术,将Rust代码编译为能在浏览器中运行的Wasm二进制文件。这使得Yew能够充分利用Rust的内存安全和并发性能优势,同时保持前端开发的灵活性和易用性。最后,Yew采用了类似于React的组件化开发模式,这一点使其能容易被接受,毕竟Reactjs的开发人员很多,其中也包括我在内。

我追求的目标是开发一个优秀的笔记系统,我希望这个系统能够完美满足我在笔记整理方面的需求。我选择使用Yew进行前端开发,主要是因为我对Rust语言的能力有信心,并且Yew采用了Reactjs的组件化开发模式。此外,我之前有多年的Reactjs开发经验,因此最终我选择Yew。

在Yew的官网上,你会看到下面的介绍,我直接截图如下:
在这里插入图片描述
都是些溢美之词,只有体验过才知道其中的滋味。

下面我挑2个我体验深刻的地方来和大家分享。

1. 目录结构

首先说一下SSR项目的目录结构,这个例子来至于我现在正在开发的项目:

.
├── Cargo.lock
├── Cargo.toml
├── index.html
├── index.scss
├── serve.sh
└── src
    ├── bin
    │   ├── ssr_hydrate.rs
    │   └── ssr_server.rs
    ├── components
    │   ├── base
    │   │   ├── button.rs
    │   │   ├── mod.rs
    │   │   ├── modal.rs
    │   ├── editor.rs
    │   ├── mod.rs
    │   └── table_component.rs
    ├── lib.rs
    └── models.rs

其中bin目录是Rust的规范,即用于存放可执行文件的入口。它对应的是Cargo.toml中的[[bin]],这一点我在之前Yew的SSR中的Cargo.toml配置中有讲解。

这个bin目录是SSR相关的。Yew的SSR讲得并不全面,给的例子也比较简单,幸好在它的examples文件夹中有simple_ssr和ssr_router这两个项目例子,不然都不知道该如何开始。

ssr_hydrate.rsssr_server.rs分别在trunk build index.htmlcargo run --bin ssr_server --features=ssr -- --dir dist这两个命令中执行。前者的作用是生成dist/index.html,后者的作用是启动服务以响应浏览器的访问。这两个文件我都是直接从simple_ssr那边拷贝过来的,然后在ssr_server.rs中添加反向代理的功能。

lib.rs是组件的入口。组件的加载都从这个文件开始。因为在Rust语言中,类型的使用会很频繁(参考已经感受到了Rust类型的一等公民地位),所以我定义了一个models.rs模块,用于存放类型和类型转换相关的代码。和Reactjs的工程一样,我也定义了一个components文件夹用于存放各种组件。

这里吐槽一下反向代理,在Yew的SSR的文档中完全没有提及这件事情,这个功能在现阶段的主流支持SSR的Javascript框架中都是有的。幸运的是,在ssr_server.rs中引入的warp 本身具备处理反向代理的能力。经过一系列的调研和摸索,最终还是实现了Yew的SSR模式中的反向代理功能。大家有兴趣可以参考Yew的SSR的反向代理代码编写。

关于这个目录结构,我体验深刻的就是它的这个bin目录。这个目录其实和我们开发的组件关系不大,里面的文件几乎不用再去维护。但是没有它,SSR的功能就跑不起来。也许做Rust开发的程序员都很强,随便可以写个服务器,因此这个bin可能根本不是它们的关注点。

2. 事件处理

在前端开发中,事件处理就像我们呼吸的空气一样,随处可见而又不自知,以至于我们忘记了它的实现原理基于闭包。然而,当涉及到Rust语言时,闭包的闭包的使用变得尤为严肃。这是因为Rust语言强调所有权的概念,闭包中使用外部数据就意味着所有权的转移,这一点需要特别注意。理解透了,在写事件代码时就会得心应手,否则,可能就会想我最开始那样,一个事件处理半天都写不出来。

下面这段Rust代码是在一个表格组件中,每一行会有一个删除按钮,点击删除按钮时触发事件,父组件负责处理这个事件。

#[function_component]
pub fn TableComponent(props: &Props) -> Html {
    let Props { on_delete, .. } = props;
    html! {
        <div class="table_component">
            <div class="table-row table-head">
                <div class="td-id cell">{"id"}</div>
                <div class="td-name cell">{"名称"}</div>
                <div class="td-create-date cell">{"创建日期"}</div>
                <div class="td-modify-date cell">{"修改日期"}</div>
                <div class="td-modify-date cell">{"操作"}</div>
            </div>
            {
                for props.data.iter().map(|row: &TableRow|{
                    let on_delete = on_delete.clone();
                    let row1 = (*row).clone();
                    html!{
                        <div class="table-row">
                            <div class="td-id cell">{row.id.clone()}</div>
                            <div class="td-name cell">{row.name.clone()}</div>
                            <div class="td-create-date cell">{row.created_date.to_string()}</div>
                            <div class="td-modify-date cell">{row.modified_date.to_string()}</div>
                            <div class="td-modify-date cell"><Button text="删除" display_type={DisplayType::Danger}  onclick={ move |_| on_delete.emit(row1.clone()) } /></div>
                        </div>
                    }
                })
            }
        </div>
    }
}

这段代码逻辑简单,我用Reactjs来重新写一遍。

import { partial } from 'lodash';

function TableComponent (props) {
  const { on_delete, data } = props;
  return (
          <div class="table_component">
            <div class="table-row table-head">
                <div class="td-id cell">{"id"}</div>
                ...
                <div class="td-modify-date cell">{"操作"}</div>
            </div>
            {
                data.map(row =>(
                        <div class="table-row">
                            <div class="td-id cell">{row.id}</div>
                            ...
                            <div class="td-modify-date cell"><Button1 text="删除" display_type="danger" onclick={partial(on_delete, row)} /></div>
                        </div>
                        )
                )
            }
        </div>
  )
}

对比一下代码,因为Javascript中变量没有所有权的约束,所以闭包的使用很简单。在Rust这边,因为有所有权的约束,大家可以看到有很多clone()。举一个困扰了我不少时间的例子,在上面的代码中,我去掉let row1 = (*row).clone(),代码如下,编译器包的错就有点让我摸不着头脑。

#[function_component]
pub fn TableComponent(props: &Props) -> Html {
    let Props { on_delete, .. } = props;
    html! {
        <div class="table_component">
            ...
            {
                for props.data.iter().map(|row: &TableRow|{
                    let on_delete = on_delete.clone();
                    // let row1 = (*row).clone();
                    html!{
                        <div class="table-row">
                            <div class="td-id cell">{row.id.clone()}</div>
                            <div class="td-name cell">{row.name.clone()}</div>
                            <div class="td-create-date cell">{row.created_date.to_string()}</div>
                            <div class="td-modify-date cell">{row.modified_date.to_string()}</div>
                            <div class="td-modify-date cell"><Button text="删除" display_type={DisplayType::Danger}  onclick={ move |_| on_delete.emit(row.clone()) } /></div>
                        </div>
                    }
                })
            }
        </div>
    }
}

编译器报错如下:

error[E0521]: borrowed data escapes outside of function
  --> src/components/table_component.rs:35:63
   |
15 | pub fn TableComponent(props: &Props) -> Html {
   |                       -----  - let's call the lifetime of this reference `'1`
   |                       |
   |                       `props` is a reference that is only valid in the function body
...
35 |                             <div class="td-modify-date cell"><Button text="删除" display_type={DisplayType::Danger}  onclick={ move |_| on_delete.emit(row.clone()) } /></div>
   |                                                               ^^^^^^
   |                                                               |
   |                                                               `props` escapes the function body here
   |                                                               argument requires that `'1` must outlive `'static`
   |

这个错误我看了好几遍,我在row上调用了clone,为什么还报所有权的错误呢?因为当进入move这个闭包函数时,所有权就发生了转移。造成这个错误的原因还是我对所有权认识不足。因此要解决这个问题,只需在进入闭包之前,先要克隆一份row的数据。那为什么在emit的调用里还要clone一次呢,那是因为外层的Callback函数里面也会发生所有权转移。所以,最后在Yew的代码中,我们会发现clone满天飞的景象。

好了,到目前为止,在和Reactjs的对比中Yew处于劣势,但是有一点是毋庸置疑的,那就是类型安全使代码编译通过就直接跑成功。所谓难者不会,会者不难,我会继续把这条路走下去,探索Rust和Yew的优势所在,希望能够得到大家的支持和鼓励。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1026288.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智慧公厕:改变公共厕所管理与运营的未来

在现代社会中&#xff0c;公共厕所是城市建设的重要组成部分。然而&#xff0c;长期以来&#xff0c;公共厕所管理与运营一直是一个令人头疼的问题。由于各种原因&#xff0c;公共厕所常常陷入管理难、环境差、设备设施陈旧的状态&#xff0c;给人们的生活带来困扰。然而&#…

【性能优化下】组织结构同步优化二,全量同步/增量同步,断点续传实现方式

看到这一篇文章的 xdm &#xff0c;应该对组织结构同步有一些想法了吧&#xff0c;如果没有&#xff0c;可以看前面两篇文章&#xff0c;可以通过如下地址查看一下&#xff1a; 【性能优化上】第三方组织结构同步优化一&#xff0c;你 get 到了吗&#xff1f; 坑爹&#xff0c…

Java中synchronized:特性、使用、锁机制与策略简析

目录 synchronized的特性互斥性可见性可重入性 synchronized的使用方法synchronized的锁机制常见锁策略乐观锁与悲观锁重量级锁与轻量级锁公平锁与非公平锁可重入锁与不可重入锁自旋锁读写锁 synchronized的特性 互斥性 synchronized确保同一时间只有一个线程可以进入同步块或…

函数扩展之——内存函数

前言&#xff1a;小伙伴们又见面啦。 本篇文章&#xff0c;我们将讲解C语言中比较重要且常用的内存函数&#xff0c;并尝试模拟实现它们的功能。 让我们一起来学习叭。 目录 一.什么是内存函数 二.内存函数有哪些 1.memcpy &#xff08;1&#xff09;库函数memcpy &…

交换机端口镜像详解

交换机端口镜像是一种网络监控技术&#xff0c;它允许将一个或多个交换机端口的网络流量复制并重定向到另一个端口上&#xff0c;以便进行流量监测、分析和记录。通过端口镜像&#xff0c;管理员可以实时查看特定端口上的流量&#xff0c;以进行网络故障排查、安全审计和性能优…

已解决 Microservice Error: Circuit Breaker: Service is temporarily unavailable

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

【操作系统】聊聊磁盘IO是如何工作的

磁盘 机械磁盘 主要是由盘片和读写磁头组成。数据存储在盘片的的环状磁道上&#xff0c;读写数据前需要移动磁头&#xff0c;先找到对应的磁道&#xff0c;然后才可以访问数据。 如果数据都在同一磁道上&#xff0c;不需要在进行切换磁道&#xff0c;这就是连续IO&#xff0c;可…

离散数学之 一阶逻辑等值演算与推理

一阶逻辑等值式与置换规则 基本等值式 这里用到了量词辖域的收缩 未完待续

电工三级证(高级)实战项目:PLC控制步进电机正反转

实训目的 了解使用PLC代替传统继电器控制回路的方法及编程技巧&#xff0c;理解并掌握步进电动机的运行方式及其实现方法。通过实验进一步加深理解步进电机控制的特点以及在实际中的应用。 控制要求 PLC设备:Siemens S7-200 要求:打开开关K0(I0.0)得电&#xff0c;启动PLC程…

【xshell和xftp连接Ubuntu教程】

一、下载xshell和xftp 下载地址 https://www.xshell.com/zh/free-for-home-school/ 二、连接xshell 输入ip&#xff0c;端口号 输入用户名&#xff0c;密码 出现这个使用就行了 三、连接xftp 同上&#xff0c;输入ip&#xff0c;端口&#xff0c;用户名&#xff0c;密码 连接成…

拓扑关系如何管理?

在设备对接涂鸦的云端过程中&#xff0c;一部分设备由于自身资源或硬件配置&#xff0c;无法直接连接云端。而是需要通过网关进行中转&#xff0c;由网关代理实现和云端进行数据交互&#xff0c;间接实现设备接入云端。这样的设备也称为子设备。 要想实现网关代理子设备接入云…

C++跳坑记:位移超出范围的处理

在C编程中&#xff0c;数据类型的选择不仅影响内存占用和性能&#xff0c;还可以对某些操作的结果产生意想不到的影响。今天&#xff0c;我将分享一个关于C在不同变量类型下位移操作结果的发现。 位移操作是C中常见的对整数的高效操作之一。然而&#xff0c;我们可能会忽视一个…

单播与多播mac地址

MAC 地址&#xff08;Media Access Control Address&#xff09;是一个用于识别网络设备的唯一标识符。每个网络设备都有一个独特的 MAC 地址&#xff0c;用于在局域网中进行通信。 单播MAC地址&#xff1a;单播MAC地址用于单播通信&#xff0c;即一对一的通信模式。当设备发送…

day4_QT

day4_QT qt绘制钟表 qt绘制钟表 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->resize(1000,1000);this->setStyleSheet("background-color:…

Word中对象方法(Methods)的理解及示例(下)

【分享成果&#xff0c;随喜正能量】当你的见识多了&#xff0c;眼界宽了&#xff0c;格局大了&#xff0c;所有的磨难都将不再是磨难&#xff0c;而是助你成长的阶梯。 。 《VBA之Word应用》&#xff08;10178982&#xff09;&#xff0c;是我推出第八套教程&#xff0c;教程…

pnpm入门教程

一、概述 1、更小 使用 npm 时&#xff0c;依赖每次被不同的项目使用&#xff0c;都会重复安装一次。 而在使用 pnpm 时&#xff0c;依赖会被存储在内容可寻址的存储中。 2、更快 依赖解析。 仓库中没有的依赖都被识别并获取到仓库。目录结构计算。 node_modules 目录结构是…

编程(47)----------Spring AOP

AOP是Spring中, 个人认为较为抽象的一个思想. 一般来说, 学习一个新东西, 第一件事是先看看这个知识点的定义是什么. 同时要注意, 同一事物的定义可以有很多, 毕竟定义没有绝对的对与错, 只有准确与否. 而初次接触AOP的定义, 第一感觉可能就是抽象, 或者说看不懂, 这里面也有…

刷题日记——将x减到0的最小操作数

将x减到0的最小操作数 题目链接&#xff1a;https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/ 题目解读 题目要求移除元素总和等于参数x&#xff0c;这道题给我的第一感觉就是从数组的两边入手&#xff0c;对数据进行加和删除&#xff0c;但是这里有一…

SVN状态图标不显示

问题可能点1&#xff1a;图标覆盖 1、右键找到设置 2、找到图标覆盖 3、重启TortoiseSVN 问题可能点2&#xff1a;注册表图标顺序太靠下&#xff0c;被占用 1、windowsr, 输入regedit进入注册表 2、找到一下目录 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Cu…

servlet中doGet方法无法读取body中的数据

servlet中doGet方法不支持读取body中的数据。