今回すること
仮想DOMツリーのノードを実装し、それを描画する関数を実装します。
また、この記事の方針として、Result
や Option
については panic しないに違いないという前提でどんどん unwrap
しています。もし実際にこのサンプルを参考にプロダクトを開発するのであれば、エラー処理を入れた方が良いかと思います。
最終的に以下の例のような出力を得ます。
準備
準備 をします。
次に cargo.toml に以下の内容を追加します。
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies.wasm-bindgen]
version = "0.2.51"
[dependencies.web-sys]
version = "0.3.4"
features = [
'Document',
'Element',
'Node',
'Text',
]
実装
仮想DOMツリーのノードを作る
// lib.rs
enum VirtualNode {
Text(&'static str),
Element {
tag_name: &'static str,
children: Vec<VirtualNode>,
},
}
仮想DOMツリーのノード VirtualNode
を実装します。<h1>Hello World</h1>
を例にすると、<h1>
タグに相当するのが VirtualNode::Element
で、Hello World
に相当するのが VirtualNode::Text
です。これを Rust のコードで表すと
VirtualNode::Element {
tag_name: "h1",
children: vec![VirtualNode::Text("Hello World")],
}
のようになります。
とりあえず、 VirtualNode::Element
にはタグ名を判別するための tag_name
と、子要素のリストを保持する children
のみを実装します。
仮想DOMを描画する
// lib.rs
fn render(document: &web_sys::Document, virtual_node: VirtualNode) -> web_sys::Node {
match virtual_node {
VirtualNode::Text(text) => document.create_text_node(text).into(),
VirtualNode::Element { tag_name, children } => {
let element = document.create_element(tag_name).unwrap();
for child in children {
element.append_child(&render(document, child));
}
element.into()
}
}
}
一旦、差分を取らずにDOMを再構築する関数を実装してみます。深さ優先な再帰を回しているだけです。いちいち web_sys::window().unwrap().document().unwrap().create_text_node(text)
などと書くのが嫌だったので、 render
の第1引数に document: &web_sys::Document
を取ってしまいました。エラー処理もコードの頭で1回すれば済むのでこの方が良いかと思います。
match
により VirtualNode
が Text
なのか Element
なのか判定します。Text
ならば document.create_text_node
によりノードを作成します。 Element
ならば document.create_element
によりノードを作成した上で、 append_child
により子ノードを追加しています。
document.create_text_node(text)
の戻り値の型は web_sys::Text
で、document.create_element(tag_name).unwrap()
の戻り値は web_sys::Element
です。共に into()
により型を web_sys::Node
に変換しています。
ここまででできたもの
上記内容にextern
や、render
関数を実行してエントリーポイントとなるエレメントにその結果を反映するmain
関数などを追加しています。
// lib.rs
extern crate wasm_bindgen;
extern crate web_sys;
use wasm_bindgen::prelude::*;
enum VirtualNode {
Text(&'static str),
Element {
tag_name: &'static str,
children: Vec<VirtualNode>,
},
}
fn render(document: &web_sys::Document, virtual_node: &VirtualNode) -> web_sys::Node {
match virtual_node {
VirtualNode::Text(text) => document.create_text_node(text).into(),
VirtualNode::Element { tag_name, children } => {
let element = document.create_element(tag_name).unwrap();
for child in children {
element.append_child(&render(document, child));
}
element.into()
}
}
}
#[wasm_bindgen(start)]
pub fn main() {
let document = web_sys::window().unwrap().document().unwrap();
let entry_point = document.get_element_by_id("app").unwrap();
let node = VirtualNode::Element {
tag_name: "h1",
children: vec![VirtualNode::Text("Hello World")],
};
entry_point
.parent_node()
.unwrap()
.replace_child(&render(&document, &node), &entry_point);
}
実行
npm start
localhost:8080 にアクセスすれば Hello World と表示されるはずです。