Rust로 가상 DOM 구현 -1

이번에



가상 DOM 트리의 노드를 구현하고 이를 그리는 함수를 구현합니다.

또한, 이 기사의 방침으로서, ResultOption 에 대해서는 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에 의해 VirtualNodeText인지 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 라고 표시됩니다.

좋은 웹페이지 즐겨찾기