JSX
想要在 ReScript 中使用一些 HTML 语法吗?如果不需要,请迅速跳过本节,假装什么也没看到!
ReScript 支持 JSX 语法,与 ReactJS 中的语法相比,有一些细微的差别。ReScript 的 JSX 并不与 ReactJS 绑定,它们会转化为正常的函数调用:
使用 ReasonReact 的读者请注意:这不是 ReasonReact 的 JSX 的转换结果,更多信息参见用法部分。
大写标签
会变成
非大写标签
会变成
Fragment
会变成
子节点
这是传递拥有两个元素的列表到子节点位置的语法。去除语法糖后,它会被转换为一个包含 child1
和 child2
的列表:
再次注意,这不是 ReasonReact 做的转换;ReasonReact 将最终的列表变成一个数组,但是这样的思路没问题。
所以很自然地,<MyComponent> myChild </MyComponent>
被转换为 @JSX MyComponent.createElement(~children=list{myChild}, ())
。也就是说,无论你做什么,传递到子节点位置的参数都会被包裹在一个列表中。如果你不想这样呢?如果你想直接传递 myChild
,而不进行额外的包装呢?
子节点展开
为了解决上面的问题,我们引入了
这将传递 myChild
的值,而不把它包装到一个列表中(在 ReasonReact 的情况下是数组),也就是转换为:
这在 myChild
已经是一个列表的情况下是非常有用的,你可以直接转发而不需要额外包装它(这将是一个类型错误)*。它还允许你在 children
位置传递任意的数据结构(记住,JSX 的 children
实际上只是一个常规的 prop)。
用法
参见 ReasonReact JSX 了解 JSX 的应用实例,它将上面的调用转化为 ReasonReact 的特定调用。
这是一个展示了大部分特性的 JSX 标签。
与 JS JSX 的不同之处
属性和子节点并没有强制要求
{}
,但为了便于学习,我们还是写出来了。一旦你格式化了你的文件,它们中的一些就会消失,一些变成了括号没有对 JSX prop 展开的支持,比如
<Comp {...props}}. />
。但我们有子节点展开:<Comp> ...children </Comp>
双关(punning)!
双关
“双关”是指标签和值的名称相同时的语法简写。例如,在 JavaScript 中,不用写 return {name: name}
,可以用 return {name}
。
Reason JSX 支持双关,<input checked />
只是 <input checked=checked />
的简写。格式化工具将帮助你尽可能地格式化到双关语法,这在有很多 prop 需要传递的情况下非常实用:
这样一来,Reason JSX 组件可以在需要额外的库来避免传递 prop 之前,塞入更多的 prop。
注意这和 ReactJS JSX 不同,ReactJS JSX 没有双关。它的 <input checked />
会转换为 <input checked=true />
,以便符合 DOM 的习惯用法,同时也为了向后兼容。
技巧和诀窍
对于想要利用 JSX 的库作者来说:上面的 @JSX
属性是潜在的 ppx 宏的一个钩子,可以定位想要格式化为 JSX 的函数。一旦你定位了这个函数,你可以把它变成任何其他表达式。
这样,每个人都能从 JSX 语法中受益,而不需要使用特定的库,例如 ReasonReact。
JSX 调用也同样支持标签参数特性:可选参数,显式传递的可选参数和默认的可选参数。
设计决策
* 你可能会想,为什么在 ReactJS 中从来不需要这样的子节点展开呢;ReactJS 使用了一些特殊的运行时逻辑 + 特殊的语法转换 + 变量参数检测和标记,来避免大多数的这些情况(看这里)。这样的动态用例使类型系统的检查变得相当复杂。由于我们控制了整个语法和 ReasonReact,我们决定引入子节点展开来区分包装和不包装的情况,这样就没有编译时和运行时开销了!