上次写颜色拾取器时,就需要一个输入框的组件,当时用文本替代的。输入框是个常用的组件,干脆一些算了,直接一写。
也不直接上代码了,喜欢的直接到工程中看源码yew-lab。
以下主要是在实现中的遇到的一些点。
三目运算符
Rust没有三目运算符,所以在处理简单HTML标签属性时,就很麻烦。
<textarea
maxlength={format!("{}", self.props.max_length)}
/>
maxlength这样的属性可能会没有设置,所以最好定义为Option<i32>这样的类型。
<textarea
maxlength={format!("{}", self.props.max_length.unwrap())}
/>
这样写如果max_length为None会报错,
panicked at 'called `Option::unwrap()` on a `None` value'
如果有三目运算符,或许我们可以这么写。
<textarea
maxlength={format!("{}", self.props.max_length.is_some()?self.props.max_length.unwrap():"")}
/>
但是实际是没有的,这时可以写一个方法,在方法里面写判断就没有问题了。
<textarea
maxlength={self.get_max_length()}
/>
pub fn get_max_length(&self) -> String {
if let Some(len) = self.props.max_length {
return format!("{}", len);
}
"".to_string();
}
但是这样渲染出来的html源码,如果maxlength为None,就会出现这样情况。
<textarea maxlength/>
虽然不影响使用,但总觉得怪怪的,最终用代码的方式解决吧。添加一个对textarea引用
<textarea
ref = {&self.textarea_ref}
/>
然后在第一次渲染完后,根据条件动态设置。
fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
if first_render {
if self.props.input_type == "textarea" {
let textarea = self.textarea_ref.cast::<HtmlTextAreaElement>().unwrap();
if let Some(maxlength) = self.props.max_length {
textarea.set_max_length(maxlength);
}
} else if self.props.input_type == "text" {
let input = self.input_ref.cast::<HtmlInputElement>().unwrap();
if let Some(maxlength) = self.props.max_length {
input.set_max_length(maxlength);
}
}
}
}
Slot
Element UI中使用了Slot这个东西,在输入框中就有不少的Slot,前缀、后缀等等。一开始我有点不知道该怎么实现这个,想着在属性中定义需要的Slot,最后发现了Yew有个Children这样的东西。
<YewInput><span slot="suffix">{"test"}</span></YewInut>
可以获取到YewInput标签中间的内容,实际上Element UI的实现也是从中间的标签中查找对应的Slot。首先需要在属性中定义一个children的属性。
#[derive(Clone, PartialEq, Properties)]
pub struct YewInputProps {
// ...
#[prop_or_default]
pub children: Children,
// ...
}
children就代表标签中间的内容,以下是如何从children中获取Slot,这个比较Rust特色。
pub fn get_slot(&self, slot_name: String) -> Option<VNode> {
for i in self.props.children.clone().into_iter() {
match i {
VNode::VTag(ref vtag) => {
match vtag.attributes {
yew::virtual_dom::Attributes::Static(vev) => {
for g in vev {
if g.0 == "slot" {
// log!(format!("{:?}", g.1));
if g.1 == slot_name {
return Some(i);
}
}
}
}
_ => {}
}
}
_ => {}
}
}
None
}
这里返回使用了Option,这个我还蛮喜欢的,之前在Java 8中就有这样的东西,然后在渲染的时候就可以动态的判断。
html!{
if let Some(node) = self.get_slot("prefix".to_string()) {
{node}
}
}
内部可修改性
这个是也是因为Slot这个东西,我一开始觉得每次都去遍历,想着缓存下吧。于是在结构内部定义了HashMap。
pub struct YewInput {
textarea_ref: NodeRef,
input_ref: NodeRef,
password_visible: bool,
hovering: bool,
focused: bool,
need_focus: bool,
// 缓存查找的
slot_map: HashMap<String, bool>,
props: YewInputProps,
}
然后我会在查找Slot是否存在方法中使用这个。
pub fn has_slot(&mut self, name: String) -> bool {
if let Some(h) = self.slot_map.get(&name.clone()) {
return *h;
}
match self.get_slot(name.clone()) {
Some(_) => {
self.slot_map.insert(name.clone(), true);
return true;
}
None => {
self.slot_map.insert(name.clone(), false);
return false;
}
}
}
这个方法之前是&self,因为改变了slot_map变成这样&mut self。这么修改后发现报出了一堆的如下的错误。
`self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
这要类的其他方法中调用这个,那么都需要改成&mut self这样的,一开始我按照提示修改了全部的报错,但最后发现这些方法会在view中调用。
fn view(&self, ctx: &Context<Self>) -> Html
这个是BaseComponent中定义的,BaseComponent是个Trait,类似于Java的接口,是不可能修改的,折腾一番最后发现白折腾。在写这几个组件的过程中,Rust语言方面的问题从没有现在遇到这个让我头大,传统的这些语言中哪会有这些问题,冷静下来一思考,觉得不可能吧,还是有一些我没有掌握的东西。翻开《Rust程序设计》这本书,在第九章结构体发现了解决方案,书中叫内部可修改性。
pub struct YewInput {
textarea_ref: NodeRef,
input_ref: NodeRef,
password_visible: bool,
hovering: bool,
focused: bool,
need_focus: bool,
// 缓存查找的
slot_map: RefCell<HashMap<String, bool>>,
props: YewInputProps,
}
slot_map定义为这样slot_map: RefCell<HashMap<String, bool>>。这样就可以不需要获取mut权限了。
pub fn has_slot(&self, name: String) -> bool {
let clone_name = name.clone();
let mut ref_cell = self.slot_map.borrow_mut();
let c = ref_cell.get(&clone_name.clone());
if c.is_some() {
return *c.unwrap();
}
match self.get_slot(name) {
Some(_) => {
ref_cell.insert(clone_name.clone(), true);
return true;
}
None => {
ref_cell.insert(clone_name.clone(), false);
return false;
}
}
}
总结
学习一门语言最好的方式就是用他来写点项目。Rust的文档不错,很多时候都是看文档来解决,最常用的是wasm-bindgen的文档。Yew相关的也更多的是看源码或者文档为主,另外也通过Element UI的源码实现,反过来再看Yew,查找相似的解决方案。另外Vue、React这类的方案已经很成熟了,所以Yew的源码阅读中也多少会有所帮助。
虽然编译器提示的错误是个很有用的东西,有些情况下按着提示会把问题解决了,就像我一开始写记事本的时候,当时遇到很多编译错误,最后按着提示终于没有错误了,后来我重读了《Rust程序设计》中关于所有权和引用相关章节的后,我几乎把当时的解决方案给重写了,所以按着提示是没有错,但或许不是最佳的方案,只是没有错误了。但像上面遇到的问题,你一步步按编译器的提示修改,最后还是解决不了问题,这时可能在换个思路了。
关于WebAssembly的东西,Mozilla这个文档非常的浅显易懂。另外《WebAssembly实战》这本书也不错,虽然是讲C/C++的,但JS和WebAssembly的相关部分,作者都是先实现了一个使用Emscripten插件的,也会提供一个没有使用插件的实现,这些都是通用的,是基础。另外既然学习了Rust了,C/C++这些其实都是一个层级的,都是会用到的。