ReScript: 2025年最強の JavaScript Alternative
Grace Collins
Solutions Engineer · Leapcell

ReScript の紹介
この言語自体には、より堅牢な型システム、より純粋な関数型プログラミングのサポート、強力な言語機能、および非常に高いパフォーマンスを備えたネイティブ言語で記述されたコンパイラなど、多くの注目すべき機能があります。もちろん、それに対応する欠点もあります。この記事では、ReScript の強力な機能、その周辺のエコシステム、および私たちの日常的な使用に最も密接に関連する React との統合について紹介することに焦点を当てます。
言語機能
ReScript の構文は、JavaScript のスーパーセットである TypeScript とは異なります。JavaScript とはかなり異なります。些細な構文については詳しく説明しません。代わりに、紹介のためにいくつかの典型的な機能を主にリストします。
型安全性
「型安全性」の意味は、Wikipedia からの引用で紹介できます。
"型システムが健全である場合、その型システムによって受け入れられた式は、(他の無関係な型の値を生成したり、型エラーでクラッシュしたりするのではなく) 適切な型の値に評価される必要があります。"
簡単に言うと、コンパイルに合格する型システムは、実行時に型エラーを生成しません。TypeScript は型安全ではありません。次の例から理由を確認できます。
// typescript // これは有効な TypeScript コードです 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; // error: x is string a.x.toFixed(0);
ReScript では、型コンパイルに合格するが、実行時に型エラーを生成するコードを記述することはできません。上記の例では、TypeScript はコンパイルできます。これは、TypeScript が構造型であり、ReScript が名目型であるためです。コード const b: U = a;
はコンパイルされません。もちろん、これだけでは型安全性を保証できません。具体的な証明プロセスは非常に学術的なので、ここでは詳しく説明しません。
型安全性の意義は、プロジェクトのセキュリティをより良く保証することにあります。大規模プロジェクトにおける TypeScript の JavaScript に対する利点と同様に、プログラムの規模が大きくなるにつれて、使用する言語が型安全である場合、リファクタリング後に実行時の型エラーを心配することなく、大胆なリファクタリングを実行できます。
イミュータブル
可変性は、データの変更を追跡および予測することを困難にする可能性があり、バグにつながる可能性があります。イミュータビリティは、コードの品質を向上させ、バグを減らすための効果的な手段です。動的言語としての JavaScript は、イミュータビリティをほとんどサポートしていません。TC39 には、現在ステージ 2 にある Record & Tuple の関連提案もあります。ReScript には、これらの 2 つのデータ型がすでに組み込まれています。
Record
ReScript の record と JavaScript の object の主な違いは次のとおりです。
- デフォルトでイミュータブル。
- record を定義するときは、対応する型を宣言する必要があります。
// rescript type person = { age: int, name: string, } let me: person = { age: 5, name: "Big ReScript" } // 年齢フィールドを更新する let meNextYear = {...me, age: me.age + 1}
ReScript は、特定の record フィールドの可変アップデートのためのエスケープハッチも提供します。
// rescript type person = { name: string, mutable age: int } let baby = {name: "Baby ReScript", age: 5} // 年齢フィールドを更新する baby.age = baby.age + 1
Tuple
TypeScript にも tuple データ型があります。ReScript の tuple の唯一の違いは、デフォルトでイミュータブルであることです。
let ageAndName: (int, string) = (24, "Lil' ReScript") // a tuple type alias type coord3d = (float, float, float) let my3dCoordinates: coord3d = (20.0, 30.5, 100.0) // Tuple を更新する let coordinates1 = (10, 20, 30) let (c1x, _, _) = coordinates1 let coordinates2 = (c1x + 50, 20, 30)
Variant
Variant は ReScript のかなり特殊なデータ構造であり、列挙型やコンストラクタ (ReScript には class の概念はありません) など、ほとんどのデータモデリングシナリオをカバーします。
// rescript // 列挙型を定義する type animal = Dog | Cat | Bird // コンストラクタ、任意の数のパラメータを渡すか、record を直接渡すことができます type account = Wechat(int, string) | Twitter({name: string, age: int})
ReScript の他の機能と組み合わせることで、variant はパターンマッチングなど、強力でエレガントな論理式機能を実現できます。これについては次に説明します。
パターンマッチング
パターンマッチングは、プログラミング言語で最も便利な機能の 1 つです。ADT (Algebraic Data Type) と組み合わせると、その表現力は従来の if および switch ステートメントよりもはるかに優れています。値を判断できるだけでなく、特定の型構造も判断できます。JavaScript にも関連する提案がありますが、ステージ 1 にあるだけであり、実際に使用できるようになるまでにはまだ長い道のりがあります。この強力な機能を紹介する前に、まず TypeScript の判別共用体の例を見てみましょう。
// typescript // タグ付き共用体 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; } }
TypeScript では、共用体の特定の型を区別する場合、区別するために kind 文字列タグを手動で追加する必要があります。この形式は比較的煩雑です。次に、ReScript がこの形式をどのように処理するかを見てみましょう。
// rescript type shape = | Circle({radius: float}) | Square({x: float}) | Triangle({x: float, y: float}) let area = (s: shape) => { switch s { // ReScript の浮動小数点数の算術演算子はドットを追加する必要があります (例: +., -., *.)。 | 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}))
variant を組み合わせて和型を構築し、パターンマッチングを使用して特定の型を一致させ、属性を分解することにより、タグを手動で追加する必要はありません。書き方とエクスペリエンスは、はるかにエレガントです。コンパイルされた JavaScript コードも実際にはタグを使用して区別しますが、ReScript を使用することで、ADT とパターンマッチングによってもたらされるメリットを享受できます。
// コンパイルされた JavaScript コード 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
NPE の問題については、TypeScript は strictNullCheck とオプションのチェーンを使用して効果的に解決できます。ReScript には、デフォルトで null および undefined 型はありません。データが空になる可能性がある場合、ReScript は Rust と同様に、組み込みの option 型とパターンマッチングを使用して問題を解決します。まず、ReScript の組み込み option 型の定義を見てみましょう。
// rescript // 'a はジェネリック型を表します type option<'a> = None | Some('a)
パターンマッチングの使用:
// 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)) }
ラベル付き引数
ラベル付き引数は、実際には名前付きパラメータです。JavaScript 自体はこの機能をサポートしていません。通常、関数パラメータが多い場合、オブジェクトの分解を使用して、貧弱なバージョンの名前付きパラメータを実装します。
const func = ({ a, b, c, d, e, f, g })=>{ }
このメソッドの好ましくない点は、オブジェクトの個別の型宣言を記述する必要があることであり、これは非常に煩雑です。次に、ReScript の構文を見てみましょう。
// rescript let sub = (~first: int, ~second: int) => first - second sub(~second = 2, ~first = 5) // 3 // エイリアス let sub = (~first as x: int, ~second as y: int) => x - y
Pipe
JavaScript にもパイプ演算子の提案があり、現在ステージ 2 にあります。パイプ演算子は、ネストされた関数呼び出しの問題を比較的エレガントに解決し、validateAge(getAge(parseData(person)))
のようなコードを回避できます。ReScript のパイプは、デフォルトでパイプファーストです。つまり、次の関数の最初のパラメータにパイプします。
// 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)
通常、JavaScript では、メソッドチェーンを使用してネストされた関数呼び出しを最適化します。以下に示すようにします。
// typescript let array = [1,2,3] let num = array.map(item => item + 2).reduce((acc,cur) => acc + cur, 0)
言及する価値があるのは、ReScript にはクラスがないため、クラスメソッドのようなものはなく、メソッドチェーンは存在しません。ReScript の多くの組み込み標準ライブラリ (配列の map や reduce など) は、データファーストのアプローチとパイプ演算子を使用して設計されており、JavaScript でおなじみのメソッドチェーンを実現します。
// rescript // ReScript 標準ライブラリで map と reduce を使用する例 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)
デコレータ
ReScript のデコレータは、TypeScript のようにクラスのメタプログラミングには使用されません。他の用途がいくつかあります (コンパイル機能や JavaScript との相互運用など)。ReScript では、モジュールをインポートして、その型を次のように定義できます。
// rescript // path モジュールの dirname メソッドを参照し、その型を string => string として宣言します @module("path") external dirname: string => string = "dirname" let root = dirname("/Leapcell/github") // "Leapcell" を返します
拡張ポイント
デコレータと同様に、JavaScript を拡張するためにも使用されますが、構文が少し異なります。たとえば、フロントエンド開発では、通常 CSS をインポートし、ビルドツールがそれに応じて処理します。ただし、ReScript のモジュールシステムには import ステートメントがなく、CSS のインポートをサポートしていません。この場合、通常 %raw を使用します。
// rescript %raw(`import "index.css";`) // コンパイルされた JavaScript の出力コンテンツ import "index.css";
React 開発
JSX
ReScript は JSX 構文もサポートしていますが、props の割り当てにはいくつかの違いがあります。
// rescript <MyComponent isLoading text onClick /> // 次と同等 <MyComponent isLoading={isLoading} text={text} onClick={onClick} />
@rescript/react
@rescript/react ライブラリは、主に react および react-dom を含む、React の ReScript バインディングを提供します。
// rescript // React コンポーネントを定義する module Friend = { @react.component let make = (~name: string, ~children) => { <div> {React.string(name)} children </div> } }
ReScript は、React コンポーネントを定義するための @react.component デコレータを提供します。make 関数はコンポーネントの特定の実装であり、ラベル付き引数を使用して props を取得します。Friend コンポーネントは、JSX で直接使用できます。
// rescript <Friend name="Leapcell" age=20 /> // JSX 糖衣構文を削除した後の ReScript コード React.createElement(Friend.make, {name: "Leapcell", age:20})
一見すると、make 関数は少し冗長に見えますが、これはいくつかの歴史的な設計上の理由によるものなので、ここではあまり詳しく説明しません。
エコシステム
JS エコシステムへの統合
JavaScript 方言の成功の重要な要素の 1 つは、既存の JavaScript エコシステムとどのように統合するかです。TypeScript が非常に人気がある理由の 1 つは、既存の JavaScript ライブラリを再利用しやすいことです。優れた d.ts ファイルを記述するだけで、TypeScript プロジェクトは既存のライブラリをスムーズにインポートして使用できます。実際、ReScript も同様です。JavaScript ライブラリの関連する ReScript 型を宣言するだけです。@rescript/react を例にとってみましょう。このライブラリは、React の ReScript 型宣言を提供します。React の createElement の型を宣言する方法を見てみましょう。
// rescript // ReactDOM.res @module("react-dom") external render: (React.element, Dom.element) => unit = "render" // render 関数を react-dom ライブラリにバインドする // ReScript のモジュールシステムでは、各ファイルはモジュールであり、モジュール名はファイル名です。インポートする必要はありません。したがって、ReactDOM.render を直接使用できます let rootQuery = ReactDOM.querySelector("#root") switch rootQuery { | Some(root) => ReactDOM.render(<App />, root) | None => () }
強力なコンパイラ
TypeScript のコンパイラは Node.js で記述されており、そのコンパイル速度は常に批判されてきました。したがって、型消去のみを実行する esbuild や swc などの TypeScript コンパイラがありますが、それでも型チェックの必要性を満たすことはできません。そのため、stc プロジェクト (Rust で記述された TypeScript 型チェッカー) も多くの注目を集めています。ReScript には、この問題について多くの心配はありません。ReScript のコンパイラはネイティブ言語 OCaml で実装されており、コンパイル速度は ReScript プロジェクトが心配して解決する必要のある問題ではありません。さらに、ReScript のコンパイラには多くの機能があります。この側面に関する詳細なドキュメントはないため、ここでは私が少し理解しているいくつかの機能のみをリストします。
定数畳み込み
定数畳み込みとは、定数式の値を計算し、最終的に生成されたコードに定数として埋め込むことを意味します。ReScript では、一般的な定数式と単純な関数呼び出しはすべて、定数畳み込みの対象となる可能性があります。
let add = (x,y) => x + y let num = add(5,3) // コンパイルされた JavaScript function add(x, y) { return x + y | 0; } var num = 8;
TypeScript で同じコードをコンパイルした結果は次のとおりです。
// typescript let add = (x:number,y:number)=>x + y let num = add(5,3) // コンパイルされた JavaScript "use strict"; let add = (x, y) => x + y; let num = add(5, 3);
型推論
TypeScript にも型推論がありますが、ReScript の方が強力です。コンテキストに基づいて型推論を実行できます。ほとんどの場合、ReScript コードを記述するときに、変数の型を宣言する必要はほとんどありません。
// rescript // フィボナッチ数列、rec は再帰関数を宣言するために使用されます let rec fib = (n) => { switch n { | 0 => 0 | 1 => 1 | _ => fib(n - 1) + fib(n - 2) } }
上記で ReScript で実装されたフィボナッチ数列関数では、変数宣言はありませんが、ReScript はパターンマッチングのコンテキストから n
が int
型であることを推論できます。同じ例では、TypeScript は n
の number
型を宣言する必要があります。
// typescript // パラメータ 'n' には暗黙的に 'any' 型があります。 let fib = (n) => { switch (n) { case 0: return 0; case 1: return 1; default: return fib(n - 1) + fib(n - 2) } }
型レイアウトの最適化
型レイアウトの最適化の機能の 1 つは、コードサイズを最適化することです。たとえば、オブジェクトを宣言するには、配列を宣言するよりも多くのコードが必要です。
let a = {width: 100, height: 200} let b = [100,200] // 難読化後 let a={a:100,b:100} let b=[100,200]
上記の例では、オブジェクト宣言の可読性を配列で置き換えることはできません。日常的な使用では、この種の最適化のためにコードの保守性を犠牲にすることはありません。ReScript では、上記のデコレータを使用して、コードを記述するときに可読性を維持でき、コンパイルされた JavaScript でコードサイズを最適化することもできます。
type node = {@as("0") width : int , @as("1") height : int} let a: node = {width: 100,height: 200} // コンパイルされた JavaScript var a = [ 100, 200 ];
ReScript は、独自の JavaScript 方言として、型システム、言語機能、React との統合、およびエコシステム統合の点で独自の利点があります。その強力なコンパイラは、開発にも多くの利便性をもたらします。TypeScript が普及している現在の環境では、ReScript はまだ比較的ニッチかもしれませんが、それが持つ機能は開発者が深く理解し、探求する価値があり、プロジェクト開発に新しいアイデアとソリューションをもたらす可能性があります。
Leapcell: Web ホスティング、非同期タスク、および Redis 向けの次世代サーバーレスプラットフォーム
最後に、Web サービスのデプロイに最適なプラットフォーム Leapcell をご紹介します。
1. 複数言語のサポート
- JavaScript、Python、Go、または Rust で開発します。
2. 無料で無制限のプロジェクトをデプロイ
- 使用量に対してのみ料金を支払います — リクエスト、料金はかかりません。
3. 比類のないコスト効率
- アイドル料金なしで、使用量に応じて支払います。
- 例: 25 ドルで、平均応答時間 60 ミリ秒で 694 万件のリクエストをサポートします。
4. 合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的な UI。
- 完全に自動化された CI/CD パイプラインと GitOps 統合。
- 実用的な洞察を得るためのリアルタイムのメトリックとロギング。
5. 簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するための自動スケーリング。
- 運用上のオーバーヘッドはゼロ — 構築に集中するだけです。
Leapcell Twitter: https://x.com/LeapcellHQ