
TypeScript bietet integrierte Unterstützung für die JSX-Syntax und der TypeScript-Compiler bietet nützliche Tools zum Einrichten des JSX-Kompilierungsprozesses. Dies ermöglicht im Wesentlichen das Schreiben von typisiertem DSL mit JSX. In diesem Artikel wird genau dies erläutert - wie man ein DSL schreibt von r mit jsx. Interessiert frage ich nach Katze.
→ Repository mit einem vorgefertigten Beispiel.
In diesem Artikel werde ich die Möglichkeiten nicht anhand von Beispielen für das Web, React und dergleichen zeigen. Ein Beispiel, das nicht aus dem Internet stammt, zeigt, dass die Funktionen von JSX nicht auf React, seine Komponenten und die Generierung von HTML im allgemeinen Fall beschränkt sind. In diesem Artikel werde ich zeigen, wie DSL implementiert wird, um Nachrichtenobjekte für Slack zu generieren.
Hier ist der Code, den wir als Grundlage nehmen. Dies ist eine kleine Nachrichtenfabrik des gleichen Typs:
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}_.` }) })
Es scheint gut auszusehen, aber es gibt einen Punkt, der erheblich verbessert werden kann - die Lesbarkeit . title_link
Sie beispielsweise auf die unverständliche color
, die beiden Felder für den Titel ( title
und title_link
) oder die Unterstriche im text
(der Text in _
wird kursiv gedruckt ). All dies hindert uns daran, Inhalte von stilistischen Details zu trennen, was es schwierig macht, das Wichtige zu finden. Und bei solchen Problemen sollte DSL helfen.
Hier ist das gleiche Beispiel, das gerade in JSX geschrieben wurde:
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>
Viel besser! Alles, was zusammenleben soll, ist vereint, stilistische Details und Inhalte sind klar voneinander getrennt - Schönheit in einem Wort.
DSL schreiben
Passen Sie das Projekt an
Zuerst müssen Sie JSX im Projekt aktivieren und dem Compiler mitteilen, dass wir React nicht verwenden und dass unser JSX anders kompiliert werden muss.
// tsconfig.json { "compilerOptions": { "jsx": "react", "jsxFactory": "Template.create" } }
"jsx": "react"
enthält JSX-Unterstützung im Projekt und der Compiler kompiliert alle JSX-Elemente in React.createElement
Aufrufe. Und die Option "jsxFactory"
konfiguriert den Compiler so, dass er unsere Factory von JSX-Elementen verwendet.
Nach diesen einfachen Einstellungen wird der Code des Formulars:
import * as Template from './template' const JSX = <message>Text with <i>italic</i>.</message>
wird in kompilieren
const Template = require('./template'); const JSX = Template.create('message', null, 'Text with ', Template.create('i', null, 'italic'), '.');
Beschreiben von JSX-Tags
Nachdem der Compiler weiß, in was JSX kompiliert werden soll, müssen wir die Tags selbst deklarieren. Zu diesem Zweck verwenden wir eine der coolen Funktionen von TypeScript - nämlich lokale Namespace-Deklarationen. Für den Fall mit JSX erwartet TypeScript, dass das Projekt über einen JSX
Namespace (der spezifische Speicherort der Datei spielt keine Rolle) mit der IntrinsicElements
Schnittstelle verfügt, in der die Tags selbst beschrieben werden. Der Compiler fängt sie ab und verwendet sie zur Typprüfung und für Hinweise.
Hier haben wir alle JSX-Tags für unser DSL und alle ihre Attribute deklariert. Tatsächlich ist der Schlüsselname in der Schnittstelle der Name des Tags selbst, das im Code verfügbar sein wird. Wert ist eine Beschreibung der verfügbaren Attribute. Einige Tags (in unserem Fall i
) haben möglicherweise keine Attribute, andere sind möglicherweise optional oder sogar erforderlich.
Fabrik selbst - Template.create
Unsere Fabrik von tsconfig.json
ist Gegenstand von Gesprächen. Es wird zur Laufzeit zum Erstellen von Objekten verwendet.
Im einfachsten Fall könnte es ungefähr so aussehen:
type Kinds = keyof JSX.IntrinsicElements
Tags, die dem Text nur Stile hinzufügen, sind einfach zu schreiben (in unserem Fall i
): Unsere Factory verpackt den Inhalt des Tags einfach in eine Zeichenfolge mit _
auf beiden Seiten. Probleme beginnen mit komplexen Tags. Die meiste Zeit habe ich mit ihnen verbracht und nach einer saubereren Lösung gesucht. Was ist das eigentliche Problem?
Und es ist so, dass der Compiler den <message>Text</message>
an einen any
druckt. Was mit einem typisierten DSL nicht in der Nähe war, na ja, okay, der zweite Teil des Problems besteht darin, dass der Typ aller Tags nach dem Durchlaufen der Factory eins ist - dies ist eine Einschränkung von JSX selbst (in React werden alle Tags in ReactElement konvertiert).
Generika helfen!
Es wurde nur Element
hinzugefügt, und jetzt gibt der Compiler alle JSX-Tags an den Element
. Dies ist auch das Standardverhalten des Compilers. Verwenden Sie JSX.Element
als Typ für alle Tags.
Unser Element
hat nur eine gemeinsame Methode - das Umwandeln in einen Nachrichtenobjekttyp. Leider funktioniert es nicht immer, nur auf dem <message/>
der obersten Ebene, und dies ist eine Zeitüberschreitung.
Und unter dem Spoiler befindet sich die Vollversion unserer Fabrik.
Werkscode selbst import { flatten } from 'lodash' type Kinds = keyof JSX.IntrinsicElements
→ Repository mit einem vorgefertigten Beispiel.
Anstelle einer Schlussfolgerung
Als ich diese Experimente durchführte, hatte das TypeScript-Team nur ein Verständnis für die Leistungsfähigkeit und die Einschränkungen dessen, was sie mit JSX machten. Jetzt sind seine Fähigkeiten noch größer und die Fabrik kann sauberer geschrieben werden. Wenn Sie das Repository anhand eines Beispiels durchsuchen und verbessern möchten - Willkommen mit Pull-Anfragen.