前言
去年的时候我就尝试用Yew写了一个记事本的应用,当时可以说是刚接触Yew,Rust也没写什么代码,好在那个应用在UI方面也比较简单。一年过去了,最近又开始折腾Yew这个框架,一开始只是想把网络请求以及图片显示等测试一下,后来觉得是不是可以写一个UI库试下,虽然也有几个在Yew推荐的UI库,但感觉审美不在一个线上。之前写系统后台管理端时,使用的是vue-element-admin这个框架,想着也写一个Yew版的吧,不过首先得把Element UI库给写出来,实际上Yew能不能大范围的使用还是跟类似Element UI这样的企业级库有关系。另一方面对于Yew,我也想通过写这个UI库,来进一步学习Yew这个框架,同时也可以练习Rust的使用。
组件
这些组件的实现都是参考了Element UI的源码,样式部分基本原封不动的使用了Element UI的样式,Element UI是基于Vue的,现在等于把Vue换成了Yew,Javascript换成了Rust,实现逻辑上依旧使用Element UI的,但在具体实现上则是根据Yew框架和Rust的特点有所不同,尽可能在功能上保持跟Element UI一致。
Button 按钮
一开始我选择了Button这个最常用的组件,这个组件相对来说比较简单。
use yew::prelude::*;
pub enum Msg {}
pub struct YewButton {
props:YewButtonProps
}
#[derive(Clone, PartialEq, Properties)]
pub struct YewButtonProps {
#[prop_or_default]
pub disabled: bool,
#[prop_or_default]
pub style: String,
pub title: AttrValue,
pub on_clicked: Callback<MouseEvent>,
#[prop_or_default]
pub loading:bool,
#[prop_or_default]
pub plain:bool,
// medium / small / mini
#[prop_or_default]
pub size:String
}
impl Component for YewButton {
type Message = Msg;
type Properties = YewButtonProps;
fn create(ctx: &Context<Self>) -> Self {
Self {
props:ctx.props().clone()
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let title = ctx.props().title.clone();
let disabled = ctx.props().disabled.clone();
let style = ctx.props().style.clone();
let loading = ctx.props().loading.clone();
let onclick = ctx.props().on_clicked.reform(move |event: MouseEvent| {
event.stop_propagation();
event.clone()
});
let mut classes = Vec::new();
classes.push(String::from("el-button"));
if !style.is_empty() {
let ss = format!("el-button--{}", style);
classes.push(ss);
}
if disabled {
classes.push(String::from("is-disabled"));
}
if self.props.plain {
classes.push(String::from("is-plain"));
}
// TODO 需要对字符串进行检查
if !self.props.size.is_empty() {
classes.push(format!("el-button--{}", self.props.size));
}
html! {
<button class={classes!(classes.clone())} {onclick} disabled={disabled.clone()} >
{title.clone()}
if loading {
<i class="el-icon-loading"></i>
}
</button>
}
}
}
这个里面代码目前没有优化,基本上时当时写的样子,应该是没有完全实现所有Element UI版的按钮功能,但核心功能是可以用的。
Rate 评分
评分组件在以前iOS的时候也是自己写过,相对按钮组件,这个就相对复杂些了。主要问题是实现半星效果的功能时,发现Yew的鼠标事件不能穿透,一开始按照原版的实现来写,结果老是出错,后来才发现是这个不能穿透的问题。
if self.is_rate_disabled() {
return false;
}
if self.props.allow_half {
let element: Element = e.target_unchecked_into();
let mut target = None;
// 这段代码原本是Element UI的实现,但是Yew的鼠标移动事件并不能穿透,所以这段代码弃用
// if element.class_list().contains("el-rate__item") {
// target = element.query_selector(".el-rate__icon").unwrap();
// }
// if target.is_some()&& target.clone().unwrap().class_list().contains("el-rate__decimal") {
// target = target.clone().unwrap().parent_element();
// } else if element.class_list().contains("el-rate__decimal") {
// target = element.parent_element();
// }
if target.is_none() {
target = Some(element);
}
if target.is_some() {
let offset_x = e.offset_x()*2;
let client_width = target.clone().unwrap().client_width();
self.pointer_at_left_half = offset_x <= client_width;
self.current_value = if self.pointer_at_left_half {
(index+1) as f64 - 0.5
} else {
(index+1) as f64
};
}
} else {
self.current_value = (index+1) as f64;
}
self.hover_index = index+1;
true
原版的实现专门对穿透做了处理,既然不支持穿透实现反而简单了,这个应该是Yew自己的设置了。
ColorPicker 颜色选择器
完成了评分组件后,想到找个再复杂一点的,于是选中了颜色选择器这个组件,一看这个组件是由好几子组件组成的,就是他了。颜色是个比较专业的东西,原版专门有个Color模块来处理,一开始我打算用Rust重写Color模块,不过鉴于Javascript的动态类型,一开始写一个Hsv转RGB的时候就卡住了,于是就找了一些第三方的库,先把功能拼凑出来,这也是颜色选择器中没有使用原版的实现的一部分。另一个遇到的问题是一些参数面板原版是支持滑动来操作的,但是我试了一下还是有问题。于是只支持了点击设置值的操作。
另一个遇到的大问题是原版支持输入颜色的操作,但是我的输入组件还没有写好,看了写源码,这个组件还需要单独去写。于是目前的功能不支持输入。
颜色处理,最后使用了csscolorparser这个库,把原来零散的库给替换了。
最后这个组件功能我觉得能用,支持十六进制颜色以及RGB和RGBA的形式,实际上csscolorparser这个库支持的很全。目前存在的问题,原版使用了Vue特有的transition标签,我一开始以为这是HTML的,后来才发现。原版弹框时会有动画,而且也会把弹出框定位到颜色按钮的下方适当位置。目前这本版本暂时没有这样的效果。
总结
Yew这个框架,我也尝试的看了下源码,这非常有帮助,因为这是文档有时无法学习到的。基本的运行原理以及生命周期之类的都有了一定的认识。另外通过这三个组件的练习,跟Vue的实现一对比,发现Vue还是包装的更深,更对开发者友好。Yew基本的功能实现是没有问题的,只是有些类似上面提到的transition这样的,可能就需要自己实现。
Rust方面,一方面我看了一些源码,这些源码使用了更深层次的Rust功能,比如宏、范型。另一方面,Rust写前端,毕竟运行环境不是操作系统,所以有些库也是特定平台的。比如数学模块,我一开始想着使用Rust自带之类的标准库,但最后还是使用js-sys的Math模块,实际证明这个很好用。
下一步,目前只写了三组件,总共有90个组件,完全是冰山一角,工作量之巨大,只能慢慢来。目前来看用Rust写前端,只是一种实验性的东西,毕竟Javascript的应用面很广,小程序,移动端,Rust更广阔的应该还是在系统应用这个层级,但面对C/C++这类系统级别的语言也是挑战,另外服务端又有各种语言占据。Rust的使用场景看似很广,但却不得不名对先占市场的问题,所以这也是我为什么要用Rust来写前端这个初衷,就是想用Rust。
最后有对Yew这个框架感兴趣的,可以关注下yew-lab这个项目,组件相关的代码都在里面。