作为一项练习,我一直在尝试在Rust中实现JSX之类的东西,以及静态类型化道具的所有额外好处。我们的想法是实现数据结构以支持此功能,然后添加一个带有宏的简单语法层。 JSX有以下重要概念:
所以,作为一个高水平,我们可以:
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());
任何想法都将受到高度赞赏。