Rust Yew应用开发的事件初探

在Rust的世界中有一个叫Yew的框架,它借鉴了React的思想。我的React代码也写了不少,今天就聊一下我个人对Yew应用开发中事件相关部分的体验。

我的也是才开始学习Rust和Yew,说得不对的地方还请大家多多指教。

下面的例子涉及到3个组件

ParentComponent组件

Button组件

<button/>原生html元素

也就是说,在ParentComponent组件中放置了Button组件,这个Button组件其实就是对<button/>的一个封装。这里的事件传递就是

<button/> -> Button -> ParentComponent

先从Button组件中的点击事件说起。

按钮事件定义如下

rust 复制代码
use yew::{Properties, Callback, MouseEvent};


#[derive(Properties, PartialEq)]
pub struct Props {
  #[prop_or_default]
  pub onclick: Callback<MouseEvent>,
}

这里上面关于事件的onclick,虽然就2行代码,但这里面的信息量比较大,我将为大家逐一分析,以能知其所以然。

Properties

用于定义组件的参数,和React一样,当组件的参数更新时,Yew框架会自动更新组件。

在Yew框架中,通过结构体(struct)来定义组件的参数。在这个结构体中,你可以定义各种类型的字段,这些字段对应的就是组件的参数。

Properties本身是一个tarit,但并不需要我们开发人员来实现这个trait。Yew框架通过Rust的derive功能,自动将Properties的实现和你定义的结构体结合起来。

在结构体的字段中,就包含了我们要讨论的事件Callback。

也就是说,我们的事件就可以定义成下面这个样子。

rust 复制代码
#[derive(Properties)]
pub struct Props {
  pub onclick: Callback<()>,
}

PartialEq

和Properties一样,PartialEq也是一个trait,如果大家结合React的组件更新逻辑,估计能够猜得出来它的用意。是的,它就是Yew框架用来比较属性值,看其是否有更新。不过你完全不必担心自己忘记加上这个trait,因为编译器的错误报告会提醒你。这也是我喜欢Rust的地方。

Callback

用于定义组件暴露出来的事件。它的声明形式如下:

rust 复制代码
pub struct Callback<IN, OUT = ()> { /* private fields */ }

当我们定义一个空事件时,代码就是这样:

rust 复制代码
pub onclick: Callback<()>

在Yew应用开发中,通常用Callback<>来定义事件,因为Yew框架本身用Callback类型包装了html元素的事件。使用Callback类型来定义事件比较方便和安全。

prop_or_default

prop_or_default其实不是必须的。如果你不为你的字段添加这个attribute 宏,就必须在使用这个组件的地方为它设置值,否者会编译报错。这对组件的使用者来说显然不太友好。

因此,这里我们使用到了prop_or_default,它可以帮助Yew框架,在运行时,如果没有为事件字段赋值,系统会为其计算推导出默认值。

在Yew框架中,除了prop_or_default,还有prop_or(value)和prop_or_else(function)。

prop_or_default: 使用类型的默认值

prop_or(value): 使用给定的默认值

prop_or_else(function): 通过函数来获取默认值

MouseEvent

yew::MouseEvent实际上是从web_sys中导出的,它包含了鼠标当前的位置信息。具体可以参考https://docs.rs/web-sys/0.3.60/web_sys/struct.MouseEvent.html

之所以把MouseEvent提出来,是为了让大家对Yew中的Html的事件参数有一个初步的了解。

rust 复制代码
pub use web_sys::AnimationEvent;
pub use web_sys::DragEvent;
pub use web_sys::ErrorEvent;
pub use web_sys::Event;
pub use web_sys::FocusEvent;
pub use web_sys::InputEvent;
pub use web_sys::KeyboardEvent;
pub use web_sys::MouseEvent;
pub use web_sys::PointerEvent;
pub use web_sys::ProgressEvent;
pub use web_sys::SubmitEvent;
pub use web_sys::TouchEvent;
pub use web_sys::TransitionEvent;
pub use web_sys::UiEvent;
pub use web_sys::WheelEvent;

上面说了这么多关于Yew事件定义,接下来看看如何从原生的<button/> html元素上获取事件吧。

使用方式和React几乎是一样的,开发过React项目的同学是不是觉得很亲切啊。

rust 复制代码
  <button style={danger_style} onclick={props.onclick.clone()}>
    {props.text.clone()}
  </button>

Button.rs的完整代码如下:

rust 复制代码
use yew::{function_component, html, Callback, Html, MouseEvent, Properties};

#[derive(PartialEq)]
pub enum DisplayType {
    Default,
    Danger,
}

#[derive(Properties, PartialEq)]
pub struct Props {
    #[prop_or_default]
    pub text: String,
    #[prop_or(DisplayType::Default)]
    pub display_type: DisplayType,
    #[prop_or_default]
    pub onclick: Callback<MouseEvent>,
}

static default_style: &str = "..."; //隐藏不必要代码
static danger_style: &str = "..."; //隐藏不必要代码

#[function_component]
pub fn Button(props: &Props) -> Html {
    match props.display_type {
        DisplayType::Danger => html! {
            <button style={danger_style} onclick={props.onclick.clone()}>
              {props.text.clone()}
            </button>
        },
        DisplayType::Default => html! {
            <button style={default_style} onclick={props.onclick.clone()}>
              {props.text.clone()}
            </button>
        },
    }
}

在上面的Button.rs中,我们创建了组件Button,并为其定义了事件onclick,下面就来看如何消费这个事件。

这里我想稍作停顿。我们知道Yew借鉴了React的思想,可以看到两者间很多的相似之处。但是毕竟Yew是基于Rust语言开发的,而React(这里特指ReactJs)是基于Javascript开发的。前者是静态编译语言,后者是动态语言。

因此,在Reat中,如何要处理Button中的onclick事件,代码应该像下面这个样子:

javascript 复制代码
<Button onclick={()=>console.log('clicked')}/>

但是在Yew中,代码就没有简单了。

没关系,只要知其所以然,其实Yew中的事件处理也很简单。

在Yew应用开发中,处理事件的步骤如下:

  1. 定义事件枚举
  2. 将事件枚举绑定到要处理的组件事件上
  3. 在当前组件的update方法中处理事件

下面的代码用struct组件为例来展示如何处理Button上的onclick事件。

定义事件枚举

rust 复制代码
use yew::{ Component }
enum ParentComponentMsg {
    Toggle,
}

pub struct ParentComponent;

impl Component for ParentComponent {
    type Message = ParentComponentMsg;
}

在上面的代码中,按照Yew的惯例,将事件枚举的名称定义为"组件名称"+Msg,建议大家也遵守这个惯例。

然后不要忘记,对于初学者来说很容易忘记下面这行代码:

rust 复制代码
type Message = ParentComponentMsg;

这行代码是告诉编译器,ParentComponent使用的事件枚举的类型。如果没有这行代码,或者这行代码是"type Message = ();",在下一个步骤上,编译器会报错。

bash 复制代码
    the trait `std::convert::From<components::base::parentComponent::ParentComponentMsg>` is not implemented for `()`

将事件枚举绑定到要处理的组件事件上

这里我们要处理Button组件的onclick事件,代码如下:

rust 复制代码
    fn view(&self, ctx: &Context<Self>) -> Html {
        let onclick = ctx.link().callback(|_| ParentComponentMsg::Toggle);
 
        html! {
            <div class="modal">
               <Button text="点击" onclick={onclick}/>
            </div>
        }
    }

上面的参数中,Context的出现频率很高,它用于获取当前组件的属性值,以及处理事件相关的处理。后面有机会再展开说说。

在当前组件的update方法中处理事件

rust 复制代码
    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            ParentComponentMsg::Toggle => {
                log!("clicked");
                // 可以在这里修改self上的属性,即改变组件本身的状态,来触发新的渲染。这就是react的做法了。
            }
        }
        true
    }

让我再来夸夸Rust,它利用它的mut关键字来告诉编译器,这个对象是可修改的。如果没有mut关键字,但有修改对象属性的代码,编译器会直接报错。这一点在ReactJs中是做不到的。在ReactJs中,我们只能严格遵守只能通过setXXX来修改状态,但如果你鬼使神差地直接修改了状态值,一般情况下好像也没有报错(我不确定某些eslint插件是否具有这个功能)。

相比于ReactJs,要处理一个事件要多些这么多的代码,是有点不方便,但是如果你的代码写错了,编译器会报错,这里总算挽回了一点面子。

好了,关于《Rust Yew应用开发的事件初探》我就写在这里了。这真的是一个初探,里面的见解比较粗浅,因为我也是边学边用。我要去写我的项目去了,有什么新发现我再来和大家分享。

有不对的地方还请大家留言指教。

相关推荐
小磊哥er6 分钟前
【前端工程化】前端开发中如何做一套规范的项目模版
前端
Wetoria16 分钟前
管理 git 分支时,用 merge 还是 rebase?
前端·git
前端开发与ui设计的老司机27 分钟前
UI前端与数字孪生融合新领域:智慧环保的污染源监测与治理
前端·ui
一只小风华~40 分钟前
Web前端开发: :has功能性伪类选择器
前端·html·html5·web
成遇1 小时前
Eslint基础使用
javascript·typescript·es6
Mr_Mao5 小时前
Naive Ultra:中后台 Naive UI 增强组件库
前端
前端小趴菜056 小时前
React-React.memo-props比较机制
前端·javascript·react.js
摸鱼仙人~7 小时前
styled-components:现代React样式解决方案
前端·react.js·前端框架
sasaraku.8 小时前
serviceWorker缓存资源
前端
RadiumAg9 小时前
记一道有趣的面试题
前端·javascript