
TypeScript具有对JSX语法的内置支持,并且TypeScript编译器提供了用于设置JSX编译过程的有用工具。 本质上,这使得使用JSX编写类型化DSL成为可能。 本文将对此进行详细讨论-如何编写DSL 从r 使用jsx。 有兴趣的,我要猫。
→ 带有示例的存储库。
在本文中,我不会通过与Web,React等相关的示例来展示这种可能性。 并非来自网络的示例将证明JSX的功能通常不仅限于React,其组件和html的生成。 在本文中,我将展示如何实现DSL来为Slack生成消息对象 。
这是我们作为基础的代码。 这是相同类型的小消息工厂:
interface Story { title: string link: string publishedAt: Date author: { name: string, avatarURL: string } } const template = (username: string, stories: Story[]) => ({ text: `:wave: ${username}, .`, attachments: stories.map(s => ({ title, color: '#000000', title_link: s.link, author_name: s.author.name, author_icon: s.author.avatarURL, text: ` _${s.publishedAt}_.` }) })
看起来不错,但是有一点可以大大改善- 可读性 。 例如,请注意不可理解的color
属性,标题的两个字段( title
和title_link
)或text
的下划线( _
内的文本将用斜体表示 )。 所有这些使我们无法将内容与风格细节分开,从而很难找到重要的内容。 对于此类问题,DSL应该会有所帮助。
这是用JSX编写的相同示例:
const template = (username: string, stories: Story[]) => <message> :wave: ${username}, . {stories.map(s => <attachment color='#000000'> <author icon={s.author.avatarURL}>{s.author.name}</author> <title link={s.link}>{s.title}</title> <i>{s.publishedAt}</i>. </attachment> )} </message>
好多了! 应该在一起的所有东西都具有统一的风格细节和内容,显然是分开的-一句话就是美丽。
编写DSL
定制项目
首先,您需要在项目中启用JSX,并告诉编译器我们没有使用React,我们的JSX需要以不同的方式进行编译。
// tsconfig.json { "compilerOptions": { "jsx": "react", "jsxFactory": "Template.create" } }
"jsx": "react"
在项目中包含JSX支持,并且编译器将所有JSX元素编译为React.createElement
调用。 然后, "jsxFactory"
选项将编译器配置为使用我们的JSX元素工厂。
在完成这些简单的设置之后,代码如下:
import * as Template from './template' const JSX = <message>Text with <i>italic</i>.</message>
将在
const Template = require('./template'); const JSX = Template.create('message', null, 'Text with ', Template.create('i', null, 'italic'), '.');
描述JSX标签
现在,编译器知道将JSX编译成什么,我们需要声明标签本身。 为此,我们使用TypeScript的一项很酷的功能-即本地名称空间声明。 对于JSX,TypeScript希望该项目具有一个JSX
命名空间(文件的特定位置无关紧要),并带有IntrinsicElements
接口,其中描述了标签本身。 编译器捕获它们并将它们用于类型检查和提示。
在这里,我们声明了DSL的所有JSX标签及其所有属性。 实际上,接口中的键名称是标签本身的名称,可以在代码中使用。 值是可用属性的描述。 一些标签(在我的情况下为i
)可能没有任何属性,其他标签则是可选的,甚至是必需的。
工厂本身Template.create
tsconfig.json
中的工厂是我们tsconfig.json
的主题。 在运行时将使用它来创建对象。
在最简单的情况下,它可能看起来像这样:
type Kinds = keyof JSX.IntrinsicElements
仅在内部文本中添加样式的标签易于编写(在本例中为i
):我们的工厂仅将标签的内容包装在字符串中,并在字符串的两边都带有_
。 问题始于复杂的标签。 我大部分时间都在和他们一起度过,寻找更清洁的解决方案。 实际问题是什么?
而且是编译器将<message>Text</message>
类型输出到any
。 这与类型化的DSL不太相近,好吧,问题的第二部分是所有标签的类型在经过工厂后都将是一种-这是JSX本身的局限性(在React中,所有标签都转换为ReactElement)。
泛型去救援!
仅添加了Element
,现在编译器将所有JSX标签输出为Element
类型。 这也是标准的编译器行为-使用JSX.Element
作为所有标记的类型。
我们的Element
只有一种常用方法-将其强制转换为消息对象类型。 不幸的是,它并不总是有效,仅在顶级<message/>
并且超时。
扰流板下方是我们工厂的完整版本。
工厂代码本身 import { flatten } from 'lodash' type Kinds = keyof JSX.IntrinsicElements
→ 带有示例的存储库。
而不是结论
当我进行这些实验时,TypeScript团队仅了解他们对JSX所做的功能和局限性。 现在,它的功能更加强大,可以简化工厂。 如果您想翻阅和改善存储库,请举一个示例-带有请求请求的Wellcome。