在Rust中实现JSX

时间:2018-01-15 14:53:02

标签: reactjs rust

作为一项练习,我一直在尝试在Rust中实现JSX之类的东西,以及静态类型化道具的所有额外好处。我们的想法是实现数据结构以支持此功能,然后添加一个带有宏的简单语法层。 JSX有以下重要概念:

  • 与XML / HTML非常相似,它是表示树的数据结构,树的节点是元素。
  • 元素:一个简单的数据结构,表示树中的一个节点,它的主要属性是:Type(Div,P或者Custom),Props(参数)和它的子节点(元素的vec)
  • 自定义组件:它们提供了将“元素”子树封装在“函数”中的功能,即它们是返回元素的函数。我通过Element.type属性支持这个自定义渲染功能的假设(参见下面的impl)
  • 自定义组件表示为具有特殊类型属性的元素。在运行“渲染”功能后,它们将被最终树中的渲染功能内容替换。
  • 一旦整个树被解析(每个自定义元素被“渲染”),树将只包含原生元素(即dom元素),这是最后一棵树
    • 最终的树可以通过遍历来呈现为字符串

所以,作为一个高水平,我们可以:

create_element(
  Div,
  DivProps { id: "myId", class: "one two three", ..DivProps::default()}, 
  vec![   
    create_element(P, PProps { ..PProps::default()}, vec![
      "Hello" // Polymorphic
    ])
  ])

然后想法是通过宏提供以下语法

<div id="myId" class="one two three">
  <p>Hello</p>
</div>

最有问题的部分之一是,这自然地定义了像这样的数据结构

struct Element<T, P>

where
 T: String for native dom elements or a custom struct for custom components,
 P: Any data structure
 {
  etype: T, 
  props: P,
  children: vec![Element<?, ?>]
}

我一直在尝试几种方法,但我对结果并不十分满意,所以这个问题是要找出是否有可能做得更好。

这是我最满意的当前实现

type Children = Vec<Rc<Render>>;

trait ElementParts {
    fn get_children(&self) -> Option<&Children>;
    fn get_tag(&self) -> String;
}


// Mostly for custom elements
trait Render: ElementParts + Debug + Display {
    // Dom elements don't have to render anything
    fn render(&self) -> Option<Rc<Render>> {
        None
    }

    // Tell whether it's a custom component that will render more things or not.
    fn should_render(&self) -> bool {
        true
    }

    // TODO Ideally this should be outside this trait
    // what bout if this is a standalong function
    // that acepts a T: RenderToString where RenderToString: Render
    // so, renderToString will have access to parts, render, should render, and anything else
    fn render_to_string(&self) -> String {
        if self.should_render() {
            match self.render() {
                Some(element) => element.render_to_string(),
                None => String::new(),
            }
        } else {
            format!("<{tag}>{}</{tag}>", self.render_children_to_string(), tag=self.get_tag())
        }
    }

    fn render_children_to_string(&self) -> String {
        if let Some(children) = self.get_children() {
            children.iter()
                .map(|child| child.render_to_string())
                .collect::<Vec<String>>()
                .join("\n")
        } else {
            String::new()
        }
    }
}




#[derive(Debug)]
struct Element<T: Debug + Clone + 'static, P: Debug + Clone + 'static> {
    etype: T,
    props: P,
    children: Children,
}



impl<P: Debug + Clone + 'static, T: Debug + Clone + 'static> Element<T, P> where Element<T, P>: Render {
    fn new(etype: T, props: P, children: Children) -> Rc<Render>
    {



        Rc::new(
            Element {
                etype: etype,
                children: children,
                props: props,
            }
        )
    }
}


impl<P: Debug + Clone + 'static, T: Debug + Clone + 'static> ElementParts for Element<T, P> {
    fn get_children(&self) -> Option<&Children> {
        Some(&self.children)
    }

    fn get_tag(&self) -> String {
        format!("{:?}", self.etype)
    }
}


impl<P: Debug + Clone + 'static, T: Debug + Clone + 'static> Display for Element<T, P> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "- {:?},  {:?}\n", self.etype, self.props).unwrap();
        for c in &self.children {
            write!(f, "  {}\n", c).unwrap();
        }

        write!(f, "\n")
    }
}


// Text node

impl ElementParts for String {
    fn get_children(&self) -> Option<&Children> {
        None
    }

    fn get_tag(&self) -> String {
        "TextNode".to_string()
    }

}

impl Render for String {
    fn should_render(&self) -> bool {
        false
    }

    fn render_to_string(&self) -> String {
        self.clone()
    }
}

#[derive(Debug, Clone)]
struct Div;

#[derive(Debug, Clone)]
struct DivProps {
    class: String,
}

impl Render for Element<Div, DivProps> {
    fn should_render(&self) -> bool {
        false
    }
}


#[derive(Debug, Clone)]
struct P;

#[derive(Debug, Clone)]
struct PProps {
    class: String,
    text: Option<String>,
}

impl Render for Element<P, PProps> {
    fn should_render(&self) -> bool {
        false
    }
}

#[derive(Debug, Clone)]
struct Name;

#[derive(Debug, Clone)]
struct NameProps {
    show: bool,
    name: String,
}

impl Render for Element<Name, NameProps> {
    fn render(&self) -> Option<Rc<Render>> {
        // In here custom logic will come
        if !self.props.show {
            return Some(Element::new(
                    P,
                    PProps{class: String::new(), text: None,},
                    vec![
                        Rc::new("NOT_SHOWING".to_string()),
                    ]
                ))
        }


        Some(
            Element::new(
                Div,
                DivProps { class: "form-control".to_string()},
                vec![
                    Rc::new("SHOWING".to_string()),
                    Rc::new(self.props.name.clone())
                ]
            )
        )
    }
}

// And this is how I would use it

        let e = Element::new(Name, NameProps{ show: true, name: "Fran".to_string() }, vec![]);
        println!("{:?}", e);
        println!("{}", e.render_to_string());

任何想法都将受到高度赞赏。

0 个答案:

没有答案