Rescript: Die beste JavaScript Alternative im Jahr 2025
Grace Collins
Solutions Engineer · Leapcell

Einführung in ReScript
Diese Sprache selbst hat viele bemerkenswerte Eigenschaften, wie z. B. ein robusteres Typsystem, mehr Unterstützung für rein funktionale Programmierung, leistungsstarke Sprachfunktionen und einen in einer nativen Sprache geschriebenen Compiler mit extrem hoher Leistung. Natürlich hat sie auch entsprechende Nachteile. Dieser Artikel konzentriert sich auf die Einführung der leistungsstarken Funktionen von ReScript, seines umliegenden Ökosystems und seiner Integration mit React, was unserem täglichen Gebrauch am nächsten kommt.
Sprachfunktionen
Die Syntax von ReScript ist nicht wie TypeScript, das eine Obermenge von JavaScript ist. Sie unterscheidet sich stark von JavaScript. Wir werden nicht auf die triviale Syntax eingehen. Stattdessen werden wir hauptsächlich einige typische Funktionen zur Einführung auflisten.
Typsicherheit
Die Bedeutung von "Typsicherheit" kann durch das Zitieren eines Satzes aus Wikipedia erläutert werden:
"Wenn ein Typsystem korrekt ist, müssen Ausdrücke, die von diesem Typsystem akzeptiert werden, zu einem Wert des entsprechenden Typs ausgewertet werden (anstatt einen Wert eines anderen, nicht verwandten Typs zu erzeugen oder mit einem Typfehler abzustürzen)"
Einfach ausgedrückt, ein Typsystem, das die Kompilierung besteht, erzeugt keine Typfehler zur Laufzeit. TypeScript ist nicht typsicher. Den Grund dafür können Sie aus dem folgenden Beispiel entnehmen:
// typescript // Dies ist ein gültiger TypeScript-Code type T = { x: number; }; type U = { x: number | string; }; const a: T = { x: 3 }; const b: U = a; b.x = "i am a string now"; const x: number = a.x; // Fehler: x ist string a.x.toFixed(0);
In ReScript können Sie keinen Code schreiben, der die Typkompilierung besteht, aber zur Laufzeit Typfehler erzeugt. Im obigen Beispiel kann TypeScript kompilieren, weil TypeScript ein struktureller Typ ist, während ReScript ein nominaler Typ ist. Der Code const b: U = a;
wird nicht kompiliert. Natürlich kann dies allein die Typsicherheit nicht garantieren. Der spezifische Beweisprozess ist recht akademisch, daher werden wir hier nicht näher darauf eingehen.
Die Bedeutung der Typsicherheit liegt in der besseren Gewährleistung der Sicherheit des Projekts. Genau wie der Vorteil von TypeScript gegenüber JavaScript in großen Projekten, wenn die Programmgröße immer größer wird, können Sie bei Verwendung einer typsicheren Sprache furchtlos Refactoring durchführen, ohne sich nach dem Refactoring um Laufzeit-Typfehler sorgen zu müssen.
Unveränderlich
Veränderlichkeit erschwert oft das Verfolgen und Vorhersagen von Datenänderungen, was zu Fehlern führen kann. Unveränderlichkeit ist ein wirksames Mittel, um die Codequalität zu verbessern und Fehler zu reduzieren. JavaScript als dynamische Sprache bietet fast keine Unterstützung für Unveränderlichkeit. TC39 hat auch einen verwandten Vorschlag für Record & Tuple, der sich derzeit in Phase 2 befindet. ReScript hat diese beiden Datentypen bereits integriert.
Record
Die Hauptunterschiede zwischen ReScript's Record und JavaScript's Object sind wie folgt:
- Standardmäßig unveränderlich.
- Beim Definieren eines Records muss ein entsprechender Typ deklariert werden.
// rescript type person = { age: int, name: string, } let me: person = { age: 5, name: "Big ReScript" } // Aktualisieren des Feldes age let meNextYear = {...me, age: me.age + 1}
ReScript bietet auch eine Escape Hatch für mutierbare Aktualisierungen bestimmter Record-Felder:
// rescript type person = { name: string, mutable age: int } let baby = {name: "Baby ReScript", age: 5} // Aktualisieren des Feldes age baby.age = baby.age + 1
Tuple
TypeScript hat auch den Tuple-Datentyp. Der einzige Unterschied im ReScript-Tuple besteht darin, dass er standardmäßig unveränderlich ist.
let ageAndName: (int, string) = (24, "Lil' ReScript") // ein Tuple-Typalias type coord3d = (float, float, float) let my3dCoordinates: coord3d = (20.0, 30.5, 100.0) // Aktualisieren des Tuples let coordinates1 = (10, 20, 30) let (c1x, _, _) = coordinates1 let coordinates2 = (c1x + 50, 20, 30)
Variant
Variant ist eine recht spezielle Datenstruktur in ReScript, die die meisten Datenmodellierungsszenarien abdeckt, wie z. B. Enumerationen und Konstruktoren (ReScript hat kein Konzept von Klasse).
// rescript // Definieren einer Enumeration type animal = Dog | Cat | Bird // Konstruktor, kann eine beliebige Anzahl von Parametern übergeben oder direkt einen Record übergeben type account = Wechat(int, string) | Twitter({name: string, age: int})
In Kombination mit anderen Funktionen von ReScript kann Variant leistungsstarke und elegante logische Ausdrucksmöglichkeiten erzielen, wie z. B. Pattern Matching, das im Folgenden behandelt wird.
Pattern Matching
Pattern Matching ist eine der nützlichsten Funktionen in Programmiersprachen. In Kombination mit ADT (Algebraic Data Type) ist seine Ausdruckskraft viel besser als herkömmliche if- und switch-Anweisungen. Es kann nicht nur Werte beurteilen, sondern auch die spezifische Typstruktur. JavaScript hat auch einen verwandten Vorschlag, der sich jedoch erst in Phase 1 befindet und noch weit davon entfernt ist, wirklich verwendbar zu sein. Bevor wir diese leistungsstarke Funktion vorstellen, betrachten wir zunächst ein Beispiel für TypeScript's Discriminated Union:
// typescript // Tagged Union type Shape = | { kind: "circle"; radius: number } | { kind: "square"; x: number } | { kind: "triangle"; x: number; y: number }; function area(s: Shape) { switch (s.kind) { case "circle": return Math.PI * s.radius * s.radius; case "square": return s.x * s.x; default: return (s.x * s.y) / 2; } }
In TypeScript müssen wir, wenn wir den spezifischen Typ eines Union-Typs unterscheiden wollen, manuell ein kind-String-Tag hinzufügen, um zu unterscheiden. Diese Form ist relativ umständlich. Sehen wir uns nun an, wie ReScript diese Form behandelt:
// rescript type shape = | Circle({radius: float}) | Square({x: float}) | Triangle({x: float, y: float}) let area = (s: shape) => { switch s { // ReScript's arithmetische Operatoren für Gleitkommazahlen müssen einen Punkt hinzufügen, z. B. +., -., *. | Circle({radius}) => Js.Math._PI *. radius *. radius | Square({x}) => x *. x | Triangle({x, y}) => x *. y /. 2.0 } } let a = area(Circle({radius: 3.0}))
Durch die Kombination von Variant zur Konstruktion eines Summentyps und die anschließende Verwendung von Pattern Matching zum Abgleich des spezifischen Typs und zur Dekonstruktion der Attribute ist es nicht erforderlich, manuell Tags hinzuzufügen. Der Schreibstil und die Erfahrung sind viel eleganter. Der kompilierte JavaScript-Code verwendet tatsächlich auch Tags zur Unterscheidung, aber durch ReScript können wir die Vorteile von ADT und Pattern Matching genießen.
// Kompilierter JavaScript-Code function area(s) { switch (s.TAG | 0) { case /* Circle */0 : var radius = s.radius; return Math.PI * radius * radius; case /* Square */1 : var x = s.x; return x * x; case /* Triangle */2 : return s.x * s.y / 2.0; } } var a = area({ TAG: /* Circle */0, radius: 3.0 });
NPE
Für das NPE-Problem kann TypeScript es jetzt effektiv durch strictNullCheck und optional Chaining lösen. ReScript hat standardmäßig keine Null- und Undefined-Typen. Für Fälle, in denen Daten leer sein können, verwendet ReScript den integrierten Option-Typ und Pattern Matching, um das Problem zu lösen, ähnlich wie Rust. Betrachten wir zunächst die Definition des integrierten Option-Typs von ReScript:
// rescript // 'a stellt einen generischen Typ dar type option<'a> = None | Some('a)
Verwendung von Pattern Matching:
// rescript let licenseNumber = Some(5) switch licenseNumber { | None => Js.log("The person doesn't have a car") | Some(number) => Js.log("The person's license number is " ++ Js.Int.toString(number)) }
Beschriftete Argumente
Beschriftete Argumente sind eigentlich benannte Parameter. JavaScript selbst unterstützt diese Funktion nicht. Wenn es viele Funktionsparameter gibt, verwenden wir normalerweise die Objektdestrukturierung, um eine Poor-Man's-Version von benannten Parametern zu implementieren.
const func = ({ a, b, c, d, e, f, g })=>{ }
Der unfreundliche Teil dieser Methode ist, dass wir eine separate Typdeklaration für das Objekt schreiben müssen, was recht umständlich ist. Sehen wir uns nun an, wie die Syntax von ReScript aussieht:
// rescript let sub = (~first: int, ~second: int) => first - second sub(~second = 2, ~first = 5) // 3 // alias let sub = (~first as x: int, ~second as y: int) => x - y
Pipe
Es gibt auch einen Vorschlag für den Pipe-Operator in JavaScript, der sich derzeit in Phase 2 befindet. Der Pipe-Operator kann das Problem verschachtelter Funktionsaufrufe relativ elegant lösen und Code wie validateAge(getAge(parseData(person)))
vermeiden. ReScript's Pipe ist standardmäßig Pipe-First, d. h. sie leitet zum ersten Parameter der nächsten Funktion weiter.
// rescript let add = (x,y) => x + y let sub = (x,y) => x - y let mul = (x,y) => x * y // (6 - 2)*3 = 12 let num1 = mul(sub(add(1,5),2),3) let num2 = add(1,5) ->sub(2) ->mul(3)
In JavaScript verwenden wir normalerweise Method Chaining, um verschachtelte Funktionsaufrufe zu optimieren, wie unten gezeigt:
// typescript let array = [1,2,3] let num = array.map(item => item + 2).reduce((acc,cur) => acc + cur, 0)
Es ist erwähnenswert, dass ReScript keine Klassen hat, daher gibt es keine Klassenmethoden und es gibt kein Method Chaining. Viele integrierte Standardbibliotheken in ReScript (wie z. B. Map und Reduce für Arrays) sind mit einem Data-First-Ansatz und dem Pipe-Operator konzipiert, um das Method Chaining zu erreichen, das wir in JavaScript gewohnt sind.
// rescript // Beispiel für die Verwendung von Map und Reduce in der ReScript-Standardbibliothek Belt.Array.map([1, 2], (x) => x + 2) == [3, 4] Belt.Array.reduce([2, 3, 4], 1, (acc, value) => acc + value) == 10 let array = [1,2,3] let num = array -> Belt.Array.map(x => x + 2) -> Belt.Array.reduce(0, (acc, value) => acc + value)
Dekorator
ReScript's Dekorator wird nicht für Metaprogrammierung in Klassen wie TypeScript's verwendet. Er hat einige andere Verwendungszwecke, z. B. für einige Kompilierungsfunktionen und für die Interoperabilität mit JavaScript. In ReScript können Sie ein Modul importieren und seinen Typ wie folgt definieren:
// rescript // Referenzieren der dirname-Methode des Pfadmoduls und Deklarieren seines Typs als string => string @module("path") external dirname: string => string = "dirname" let root = dirname("/Leapcell/github") // gibt "Leapcell" zurück
Erweiterungspunkt
Ähnlich wie der Dekorator wird er auch verwendet, um JavaScript zu erweitern, aber die Syntax ist etwas anders. In der Frontend-Entwicklung importieren wir beispielsweise normalerweise CSS, und das Build-Tool behandelt dies entsprechend. Das Modulsystem von ReScript hat jedoch keine Import-Anweisung und unterstützt das Importieren von CSS nicht. In diesem Fall verwenden wir normalerweise %raw.
// rescript %raw(`import "index.css";`) // Der Ausgabekontent des kompilierten JavaScript import "index.css";
React-Entwicklung
JSX
ReScript unterstützt auch die JSX-Syntax, aber es gibt einige Unterschiede bei der Props-Zuweisung:
// rescript <MyComponent isLoading text onClick /> // Äquivalent zu <MyComponent isLoading={isLoading} text={text} onClick={onClick} />
@rescript/react
Die @rescript/react-Bibliothek bietet hauptsächlich ReScript-Bindings für React, einschließlich React und React-Dom.
// rescript // Definieren einer React-Komponente module Friend = { @react.component let make = (~name: string, ~children) => { <div> {React.string(name)} children </div> } }
ReScript bietet den @react.component-Dekorator zum Definieren von React-Komponenten. Die Make-Funktion ist die spezifische Implementierung der Komponente, die beschriftete Argumente verwendet, um Props zu erhalten. Die Friend-Komponente kann direkt in JSX verwendet werden.
// rescript <Friend name="Leapcell" age=20 /> // ReScript-Code nach dem Entfernen von JSX-Syntaxzucker React.createElement(Friend.make, {name: "Leapcell", age:20})
Auf den ersten Blick scheint die Make-Funktion etwas redundant zu sein, aber dies ist auf einige historische Designgründe zurückzuführen, daher werden wir hier nicht zu sehr ins Detail gehen.
Ökosystem
Integration in das JS-Ökosystem
Einer der Schlüsselfaktoren für den Erfolg eines JavaScript-Dialekts ist die Integration in das bestehende JavaScript-Ökosystem. Einer der Gründe, warum TypeScript so beliebt ist, ist, dass es einfach ist, vorhandene JavaScript-Bibliotheken wiederzuverwenden. Sie müssen nur gute d.ts-Dateien schreiben, und ein TypeScript-Projekt kann diese problemlos importieren und verwenden. Tatsächlich ist ReScript ähnlich. Sie müssen nur die relevanten ReScript-Typen für JavaScript-Bibliotheken deklarieren. Nehmen wir @rescript/react als Beispiel. Diese Bibliothek bietet ReScript-Typdeklarationen für React. Sehen wir uns an, wie der Typ für React's createElement deklariert wird:
// rescript // ReactDOM.res @module("react-dom") external render: (React.element, Dom.element) => unit = "render" // Binden der Render-Funktion an die react-dom-Bibliothek // Im Modulsystem von ReScript ist jede Datei ein Modul, und der Modulname ist der Dateiname. Es ist nicht erforderlich zu importieren, sodass Sie ReactDOM.render direkt verwenden können let rootQuery = ReactDOM.querySelector("#root") switch rootQuery { | Some(root) => ReactDOM.render(<App />, root) | None => () }
Leistungsstarker Compiler
Der Compiler von TypeScript ist in Node.js geschrieben, und seine Kompilierungsgeschwindigkeit wurde immer kritisiert. Daher gibt es TypeScript-Compiler wie esbuild und swc, die nur Type Erasure durchführen, aber sie können immer noch nicht den Bedarf an Typüberprüfung decken. Daher hat auch das stc-Projekt (TypeScript-Typchecker, geschrieben in Rust) viel Aufmerksamkeit erregt. ReScript hat keine großen Sorgen in Bezug auf dieses Problem. Der Compiler von ReScript ist in der Muttersprache OCaml implementiert, und die Kompilierungsgeschwindigkeit wird kein Problem sein, um das sich ReScript-Projekte sorgen und lösen müssen. Darüber hinaus verfügt der Compiler von ReScript über viele Funktionen. Da es keine detaillierte Dokumentation zu diesem Aspekt gibt, listen wir hier nur einige Funktionen auf, von denen ich ein wenig verstehe.
Constant Folding
Constant Folding bedeutet, den Wert eines konstanten Ausdrucks zu berechnen und ihn als Konstante in den endgültigen generierten Code einzubetten. In ReScript können gängige konstante Ausdrücke und einfache Funktionsaufrufe einem Constant Folding unterzogen werden.
let add = (x,y) => x + y let num = add(5,3) // Kompiliertes JavaScript function add(x, y) { return x + y | 0; } var num = 8;
Das Kompilierungsergebnis desselben Codes in TypeScript ist wie folgt:
// typescript let add = (x:number,y:number)=>x + y let num = add(5,3) // Kompiliertes JavaScript "use strict"; let add = (x, y) => x + y; let num = add(5, 3);
Typinferenz
TypeScript hat auch eine Typinferenz, aber die von ReScript ist leistungsfähiger. Sie kann eine kontextbasierte Typinferenz durchführen. In den meisten Fällen müssen Sie kaum Typen für Variablen deklarieren, wenn Sie ReScript-Code schreiben.
// rescript // Fibonacci-Sequenz, rec wird verwendet, um eine rekursive Funktion zu deklarieren let rec fib = (n) => { switch n { | 0 => 0 | 1 => 1 | _ => fib(n - 1) + fib(n - 2) } }
In der obigen in ReScript implementierten Fibonacci-Sequenzfunktion gibt es keine Variablendeklaration, aber ReScript kann aus dem Kontext des Pattern Matching schließen, dass n
vom Typ int
ist. Im selben Beispiel muss TypeScript den Typ number
für n
deklarieren.
// typescript // Parameter 'n' hat implizit einen 'any'-Typ. let fib = (n) => { switch (n) { case 0: return 0; case 1: return 1; default: return fib(n - 1) + fib(n - 2) } }
Typ-Layout-Optimierung
Eine der Funktionen der Typ-Layout-Optimierung ist die Optimierung der Code-Größe. Beispielsweise erfordert das Deklarieren eines Objekts mehr Code als das Deklarieren eines Arrays.
let a = {width: 100, height: 200} let b = [100,200] // Nach der Verschleierung let a={a:100,b:100} let b=[100,200]
Im obigen Beispiel kann die Lesbarkeit von ObjekDeklarationen nicht durch Arrays ersetzt werden. Im täglichen Gebrauch werden wir die Code-Wartbarkeit nicht für diese Art von Optimierung opfern. In ReScript können wir durch den oben erwähnten Dekorator während des Schreibens von Code die Lesbarkeit beibehalten, und das kompilierte JavaScript kann auch die Code-Größe optimieren.
type node = {@as("0") width : int , @as("1") height : int} let a: node = {width: 100,height: 200} // Kompiliertes JavaScript var a = [ 100, 200 ];
Als einzigartiger JavaScript-Dialekt hat ReScript seine eigenen Vorteile in Bezug auf Typsystem, Sprachfunktionen, Integration mit React und Ökosystemintegration. Sein leistungsstarker Compiler bringt auch viele Annehmlichkeiten für die Entwicklung. Obwohl ReScript in der aktuellen Umgebung, in der TypeScript beliebt ist, möglicherweise noch relativ eine Nische besetzt, sind die Funktionen, die es besitzt, es wert, von Entwicklern eingehend verstanden und erforscht zu werden, und es kann neue Ideen und Lösungen für die Projektentwicklung bringen.
Leapcell: Die Serverlose Plattform der nächsten Generation für Webhosting, asynchrone Aufgaben und Redis
Abschließend möchte ich Ihnen eine Plattform vorstellen, die sich am besten für die Bereitstellung von Webdiensten eignet: Leapcell
1. Multi-Sprachunterstützung
- Entwickeln Sie mit JavaScript, Python, Go oder Rust.
2. Stellen Sie unbegrenzt Projekte kostenlos bereit
- Zahlen Sie nur für die Nutzung – keine Anfragen, keine Gebühren.
3. Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 US-Dollar unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
4. Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollautomatisierte CI/CD-Pipelines und GitOps-Integration.
- Echtzeit-Metriken und -Protokollierung für umsetzbare Erkenntnisse.
5. Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Null Betriebsaufwand – konzentrieren Sie sich einfach auf den Aufbau.
Erfahren Sie mehr in der Dokumentation!
Leapcell Twitter: https://x.com/LeapcellHQ