
JavaScript ist eine der Sprachen mit dynamischer Typisierung. Solche Sprachen eignen sich für eine schnelle Anwendungsentwicklung. Wenn jedoch mehrere Teams mit der Entwicklung eines großen Projekts beginnen, ist es besser, von Anfang an eines der Tools zur Typprüfung auszuwählen.
Sie können mit der Entwicklung von TypeScript-Code beginnen oder ihn in ein Flow-Projekt aufnehmen. TypeScript ist eine kompilierte Version von JavaScript, die von Microsoft entwickelt wurde. Flow ist im Gegensatz zu TypeScript keine Sprache, sondern ein Tool, mit dem Sie Code analysieren und Typen überprüfen können. Im Internet finden Sie viele Artikel und Videos zu diesen Ansätzen sowie eine Anleitung zum Starten der Eingabe. In diesem Artikel möchten wir Ihnen erklären, warum Flow nicht zu uns passte und wie wir zu Typescript gewechselt sind.
Ein bisschen Geschichte
2016 haben wir begonnen, einen React / Redux-basierten Webclient für unser ECM-System zu entwickeln. Flow wurde aus folgenden Gründen ausgewählt, um die Eingabe zu testen:
- React und Flow sind Produkte der gleichen Firma Facebook.
- Flow entwickelte sich aktiver.
- Flow lässt sich problemlos in das Projekt integrieren.
Das Projekt wuchs jedoch, die Anzahl der Entwicklungsteams nahm zu und bei der Verwendung von Flow traten einige Probleme auf:
- Überprüfung des Hintergrundtyps Flow verwendete zu viele PC-Ressourcen. Infolgedessen haben einige Entwickler es deaktiviert und bei Bedarf mit der Überprüfung begonnen.
- Es gab Situationen, in denen es genauso lange dauerte, den Code mit Flow in Einklang zu bringen, wie den Code selbst zu schreiben.
- Der Code wurde im Projekt angezeigt und war nur erforderlich, um den Flow-Test zu bestehen. Beispiel: Doppelte Überprüfung auf Null:
foo() { if (this.activeFormContainer == null) { return; }
- Die meisten Entwickler verwendeten den Visual Studio Code-Code-Editor, in dem Flow nicht so gut unterstützt wird wie TypeScript. Die automatische Vervollständigung (IntelliSense) funktionierte während der Entwicklung nicht immer und die Code-Navigation war instabil. Ich möchte den gleichen Entwicklungskomfort haben wie beim Schreiben in C # in Visual Studio.
Einige Entwickler hatten die Idee, zu TypeScript zu wechseln. Um die Idee des Übergangs zu testen und das Management zu überzeugen, haben wir uns entschlossen, den Prototyp auszuprobieren.
Prototyp
Wir wollten zwei Ideen an Prototypen testen:
- Versuchen Sie, das gesamte Projekt zu übersetzen.
- Richten Sie das Projekt so ein, dass Sie Flow und Typescript parallel verwenden können.
Für die erste Idee wurde ein Dienstprogramm benötigt, das alle Projektdateien konvertiert. Im Netzwerk fand man eine davon. Nach der Beschreibung zu urteilen, konnte sie am meisten übersetzen, aber einige der Änderungen müssten von uns selbst bearbeitet werden, oder das Dienstprogramm selbst sollte hinzugefügt werden. Wir konnten ein Testprojekt mit einer kleinen Anzahl von Dateien konvertieren. Das eigentliche Projekt konnte jedoch nicht kompiliert werden, es wäre notwendig gewesen, zu viele Dateien zu bearbeiten. Wir haben beschlossen, nicht in diese Richtung weiterzumachen, da:
- Es gab noch viel zu tun! Und während wir das Projekt abschließen, werden die verbleibenden Teams weiterhin neue Funktionen entwickeln, Fehler beheben und Tests schreiben. Außerdem würde das Zusammenführen von Dateien viel Zeit in Anspruch nehmen.
- Selbst wenn wir das Projekt auf diese Weise übersetzen würden, wie viel Arbeit müssten unsere Tester dann leisten!
Obwohl wir diese Option aufgegeben haben, haben wir nützliche Erfahrungen damit gesammelt. Es wurde deutlich, wie viel Arbeit ungefähr erforderlich ist, um jede Datei zu übersetzen. So sieht die Übersetzung einer einfachen React-Komponente aus.

Wie Sie sehen, gibt es nicht viele Änderungen. Grundsätzlich sind sie wie folgt:
- entferne // @ flow;
- Ersetzen Sie den Typ durch eine vertraute Oberfläche.
- Zugriffsmodifikatoren hinzufügen;
- Ersetzen Sie Typen durch Typen aus ts-Bibliotheken (aus dem Beispiel im Bild: Ereignishandler und die Ereignisse selbst).
Die Implementierung der zweiten Idee würde eine weitere Entwicklung ermöglichen, jedoch bereits in TypeScript und im Hintergrund, um die vorhandene Codebasis langsam zu übersetzen. Dies bot mehrere Vorteile:
- Einfach zu übersetzen, ohne Angst zu haben, etwas zu verpassen.
- Einfach zu testen.
- Einfache Zusammenführung von Änderungen.
Es war jedoch nicht ganz klar, ob das Projekt so konfiguriert werden konnte, dass zwei Arten der Eingabe parallel ausgeführt werden können. Eine Suche im Internet führte zu nichts Konkretem, also begannen sie, es selbst zu klären. Theoretisch prüft der Flow Analyzer nur Dateien mit der Erweiterung js / jsx und enthält einen Kommentar:
Für den TypeScript-Compiler müssen Dateien die Erweiterung ts / tsx haben. Daraus folgt, dass beide Schreibansätze gleichzeitig funktionieren und sich nicht gegenseitig stören sollten. Auf dieser Grundlage haben wir die Projektumgebung eingerichtet. Aus der Erfahrung des ersten Prototyps haben wir einige Dateien übersetzt. Projekt kompiliert, Client gestartet - alles hat wie bisher funktioniert!
Grünes Licht
Und eines schönen Tages - am Tag der Sprintplanung - hat unser Team im Backlog eine User Story „Start Switching to TypeScript“ mit der folgenden Liste von Arbeiten:
- Richten Sie das Webpack ein.
- Konfigurieren Sie tslint.
- Richten Sie eine Testumgebung ein.
- Übersetzen Sie Dateien in TypeScript.
Webpack-Setup
Der erste Schritt besteht darin, Webpack den Umgang mit Dateien mit der Erweiterung ts / tsx beizubringen. Zu diesem Zweck haben wir dem Abschnitt "Regeln" der Konfigurationsdatei eine Regel hinzugefügt. Ursprünglich verwendeter ts-loader:
Um die Assembly zu beschleunigen, wurde die Typprüfung
transpileOnly: true
, weil Die IDE zeigt bereits Fehler beim Schreiben von Code an.
Als wir jedoch mit der Übersetzung unserer Redux-Aktionen begannen, wurde klar, dass sie das
Plugin babel-plugin-transform-class-display-name benötigen,
um zu funktionieren. Dieses Plugin fügt allen Klassen eine statische displayName-Eigenschaft hinzu. Nach der Übersetzung wurden nur ts-loader-Aktionen verarbeitet, sodass keine babel-Plugins auf sie angewendet werden konnten. Infolgedessen haben wir ts-loader aufgegeben und die vorhandene Regel für js / jsx durch Hinzufügen von
babel / preset-typescript erweitert:
Damit der TypeScript-Compiler ordnungsgemäß funktioniert, müssen Sie die Konfigurationsdatei tsconfig.json hinzufügen, die der Dokumentation entnommen wurde.
Konfigurieren Sie Tslint
Mit Flow geschriebener Code wurde zusätzlich mit eslint überprüft. TypeScript hat sein Gegenstück, tslint. Anfangs wollte ich alle Regeln von eslint auf tslint übertragen. Es wurde versucht, Regeln über das Plugin tslint-eslint-rules zu synchronisieren, aber die meisten Regeln werden nicht unterstützt. Es ist auch möglich, eslint zu verwenden, um ts-Dateien mit dem Typenskript-eslint-Parser zu überprüfen. Leider kann nur ein Parser mit eslint verbunden werden. Wenn Sie nur ts-parser für alle Dateitypen verwenden, treten sowohl in js-Dateien als auch in ts viele seltsame Fehler auf. Infolgedessen haben wir die empfohlenen Regeln verwendet, die auf unsere Anforderungen erweitert wurden:
// tslint.json "extends": ["tslint:recommended", "tslint-react"]
Übersetzen Sie eine Datei in TypeScript
Jetzt ist alles fertig und Sie können mit der Übersetzung von Dateien beginnen. Zunächst haben wir uns entschlossen, eine kleine React-Komponente zu übertragen, die im gesamten Projekt verwendet wird. Die Wahl fiel auf die Komponente „Button“.

Während des Übersetzungsprozesses ist ein Problem aufgetreten: Nicht alle Bibliotheken von Drittanbietern verfügen über TypeScript-Typisierung, z. B. bem-cn-lite. In der
TypeSearch- Ressource von Microsoft wurde die
Typbibliothek dafür nicht gefunden. Für fast alle erforderlichen Bibliotheken haben wir Bibliotheken vom Typ ts gefunden und verbunden. Eine Lösung bestand darin, eine Verbindung über erforderlich herzustellen:
const b = require('bem-cn-lite');
Gleichzeitig wurde das Problem mit dem Mangel an Typen nicht gelöst. Aus diesem Grund haben wir mit dem Dienstprogramm
dts-gen selbst einen „Stub“ für die Typen generiert:
dts-gen -m bem-cn-lite
Das Dienstprogramm hat eine Datei mit der Erweiterung * .d.ts generiert. Die Datei wurde im Ordner @types abgelegt und tsconfig.json konfiguriert:
// tsconfig.json "typeRoots": [ "./@types", "./node_modules/@types" ]
Als nächstes haben wir in Analogie zum Prototyp die Komponente übersetzt. Das Projekt kompiliert, den Client gestartet - alles hat funktioniert! Aber die Tests brachen ab.
Setup der Testumgebung
Zum Testen der Anwendung verwenden wir Storybook und Mocha.
Das Storybook wird für visuelle Regressionstests verwendet (
Artikel ). Wie das Projekt selbst wird es mit Webpack erstellt und verfügt über eine eigene Konfigurationsdatei. Um mit ts / tsx-Dateien arbeiten zu können, musste sie daher analog zur Konfiguration des Projekts selbst konfiguriert werden.
Während wir das Projekt mit ts-loader erstellt haben, haben wir die Ausführung von Mocha-Tests eingestellt. Fügen Sie der Testumgebung ts-node hinzu, um dieses Problem zu lösen:
// mocha.opts --require @babel/polyfill --require @babel/register --require test/index.js --require tsconfig-paths/register --require ts-node/register/transpile-only --recursive --reporter mochawesome --reporter-options reportDir=../../bin/TestResults,reportName=js-test-results,inlineAssets=true --exit
Aber nachdem Sie zu Babel gewechselt sind, können Sie es loswerden.
Die Probleme
Während des Übersetzungsprozesses stießen wir auf eine große Anzahl von Problemen unterschiedlicher Komplexität. Sie waren hauptsächlich auf unsere mangelnde Erfahrung mit TypeScript zurückzuführen. Hier sind einige davon:
- Importieren Sie Komponenten / Funktionen aus verschiedenen Dateitypen.
- Übersetzung von Komponenten höherer Ordnung.
- Verlust der Änderungsgeschichte.
Importieren Sie Komponenten / Funktionen aus verschiedenen Dateitypen
Bei der Verwendung von Komponenten / Funktionen aus verschiedenen Dateitypen musste die Dateierweiterung angegeben werden:
import { foo } from './utils.ts'
Auf diese Weise können Sie den Webpack- und eslint-Konfigurationsdateien gültige Erweiterungen hinzufügen:
Übersetzung von Komponenten höherer Ordnung
Von allen Dateitypen verursachte die Übersetzung von Komponenten höherer Ordnung (HOC) die meisten Probleme. Dies ist eine Funktion, die eine Komponente am Eingang nimmt und eine neue Komponente zurückgibt. Es wird hauptsächlich zur Wiederverwendung von Logik verwendet. Beispielsweise kann es eine Funktion sein, die die Möglichkeit bietet, Elemente auszuwählen:
const MyComponentWithSeletedItem = withSelectedItem(MyComponent);
Oder die berühmteste Verbindung aus der Redux-Bibliothek. Die Eingabe solcher Funktionen ist nicht trivial und erfordert das Anschließen einer zusätzlichen Bibliothek, um mit Typen arbeiten zu können. Ich werde den Übersetzungsprozess nicht im Detail beschreiben, da Sie im Internet viele Handbücher zu diesem Thema finden. Kurz gesagt, das Problem ist, dass eine solche Funktion abstrakt ist: Jede Komponente mit einem beliebigen Satz von Eigenschaften kann Eingaben akzeptieren. Dies kann eine Button-Komponente mit den Eigenschaften title und onClick oder eine Picture-Komponente mit den Eigenschaften alt und imgUrl sein. Die Menge dieser Eigenschaften ist uns nicht im Voraus bekannt, nur die Eigenschaften, die die Funktion selbst hinzufügt, sind bekannt. Damit der TypeScript-Compiler bei der Verwendung von Komponenten, die mit Hilfe solcher Funktionen erhalten wurden, nicht schwört, müssen die Eigenschaften, die die Funktion aus dem Rückgabetyp hinzufügt, "ausgeschnitten" werden.
Dazu benötigen Sie:
- Ziehen Sie diese Eigenschaften in die Benutzeroberfläche:
interface IWithSelectItem { selectedItem: number; handleSelectedItemChange: (id: number) => void; }
- Entfernen Sie alle Eigenschaften, die in die IWithSelectItem-Schnittstelle eingegeben werden, von der Komponentenschnittstelle. Dazu können Sie die Operation Diff <T, U> aus der Bibliothek der Dienstprogrammtypen verwenden .
React.ComponentType<Diff<TPropsComponent, IWithSelectItem>>
Verlust der Änderungsgeschichte
Um mit Quellen zu arbeiten, z. B. Codeüberprüfung, verwenden wir Team Foundation Server. Bei der Übersetzung von Dateien sind wir auf eine unangenehme Funktion gestoßen. Anstelle einer geänderten Datei werden zwei im Anforderungspool angezeigt:
- remote - die alte Version der Datei;
- erstellt - neue Version.

Dieses Verhalten wird beobachtet, wenn die Datei viele Änderungen aufweist (Ähnlichkeit <50%), z. B. bei kleinen Dateien. Um dieses Problem zu lösen, haben wir versucht:
- git mv befehl
- Führen Sie zwei Commits aus: Der erste ändert die Dateierweiterung, der zweite enthält sofortige Korrekturen.
Leider haben uns beide Ansätze nicht geholfen.
Zusammenfassung
Verwenden Sie Flow oder TypeScript - jeder entscheidet für sich selbst, beide Ansätze haben ihre Vor- und Nachteile. Wir haben uns für TypeScript entschieden. Und Sie waren aus eigener Erfahrung überzeugt: Wenn Sie sich für einen der Ansätze entschieden und plötzlich festgestellt haben, dass er auch nach drei Jahren nicht zu Ihnen passt, können Sie ihn jederzeit ändern. Und für einen reibungsloseren Übergang können Sie das Projekt wie uns so konfigurieren, dass es parallel arbeitet.
Zum Zeitpunkt des Schreibens haben wir noch nicht vollständig auf TypeScript umgestellt, aber wir haben den Hauptteil - den "Kern" des Projekts - bereits neu geschrieben. In der Codebasis finden Sie Beispiele für die Übersetzung aller Arten von Dateien, von einer einfachen Reaktionskomponente zu Komponenten höherer Ordnung. Außerdem wurden Schulungen für alle Entwicklungsteams durchgeführt, und jetzt überträgt jedes Team im Rahmen seiner Aufgabe einen Teil des Projekts auf diese Aufgaben.
Wir planen, den Übergang vor Ende des Jahres abzuschließen, Tests und ein Storybook zu übersetzen und vielleicht sogar einige unserer tslint-Regeln zu schreiben.
Nach meinen persönlichen Gefühlen kann ich sagen, dass die Entwicklung weniger Zeit in Anspruch nahm, die Typprüfung im laufenden Betrieb durchgeführt wurde, ohne das System zu laden, und dass Fehlermeldungen für mich persönlich verständlicher wurden.